This commit is contained in:
2025-09-27 22:57:59 +08:00
parent 8a458ff0a4
commit ed26244cdb
12585 changed files with 1914308 additions and 3474 deletions

View File

@@ -4,92 +4,215 @@ const common_http = require("../../common/http.js");
const _sfc_main = {
data() {
return {
form: { phone: "", password: "" },
phoneFocused: false,
passwordFocused: false
loading: false,
tab: "login",
loginForm: { email: "", password: "" },
regForm: { name: "", email: "", code: "", password: "", password2: "" },
resetForm: { email: "", code: "", password: "", password2: "" },
regCountdown: 0,
resetCountdown: 0,
_timers: []
};
},
beforeUnmount() {
this._timers.forEach((t) => clearInterval(t));
},
methods: {
validate() {
const p = String(this.form.phone || "").trim();
const okPhone = /^1[3-9]\d{9}$/.test(p);
if (!okPhone) {
common_vendor.index.showToast({ title: "请输入正确的手机号", icon: "none" });
return false;
toast(msg) {
try {
common_vendor.index.showToast({ title: String(msg || "操作失败"), icon: "none" });
} catch (_) {
}
return true;
},
validateEmail(v) {
return /^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$/.test(String(v || "").trim());
},
startCountdown(key) {
if (this[key] > 0)
return;
this[key] = 60;
const timer = setInterval(() => {
this[key] = Math.max(0, this[key] - 1);
if (this[key] === 0)
clearInterval(timer);
}, 1e3);
this._timers.push(timer);
},
async onLogin() {
if (!this.validate())
return;
const { email, password } = this.loginForm;
if (!this.validateEmail(email))
return this.toast("请输入正确邮箱");
if (!password || password.length < 6)
return this.toast("请输入至少6位密码");
this.loading = true;
try {
const phone = String(this.form.phone || "").trim();
const password = String(this.form.password || "");
const res = await common_http.post("/api/auth/password/login", { phone, password });
if (res && res.token) {
common_vendor.index.setStorageSync("TOKEN", res.token);
if (res.user && res.user.phone)
common_vendor.index.setStorageSync("USER_MOBILE", res.user.phone);
common_vendor.index.showToast({ title: "登录成功", icon: "none" });
setTimeout(() => {
common_vendor.index.reLaunch({ url: "/pages/index/index" });
}, 200);
}
const data = await common_http.post("/api/auth/password/login", { email, password });
this.afterLogin(data);
} catch (e) {
common_vendor.index.showToast({ title: e && e.message || "登录失败", icon: "none" });
this.toast(e.message);
} finally {
this.loading = false;
}
},
onGoRegister() {
common_vendor.index.navigateTo({
url: "/pages/auth/register"
});
afterLogin(data) {
try {
if (data && data.token) {
common_vendor.index.setStorageSync("TOKEN", data.token);
if (data.user && data.user.shopId)
common_vendor.index.setStorageSync("SHOP_ID", data.user.shopId);
common_vendor.index.setStorageSync("ENABLE_DEFAULT_USER", "false");
common_vendor.index.removeStorageSync("DEFAULT_USER_ID");
this.toast("登录成功");
setTimeout(() => {
common_vendor.index.reLaunch({ url: "/pages/index/index" });
}, 300);
} else {
this.toast("登录失败");
}
} catch (_) {
this.toast("登录失败");
}
},
async sendRegCode() {
if (!this.validateEmail(this.regForm.email))
return this.toast("请输入正确邮箱");
this.loading = true;
try {
const r = await common_http.post("/api/auth/email/send", { email: this.regForm.email, scene: "register" });
if (r && r.ok)
this.startCountdown("regCountdown");
this.toast(r && r.ok ? "验证码已发送" : "发送过于频繁");
} catch (e) {
this.toast(e.message);
} finally {
this.loading = false;
}
},
async onRegister() {
const f = this.regForm;
if (!f.name || f.name.trim().length < 1)
return this.toast("请输入用户名");
if (!this.validateEmail(f.email))
return this.toast("请输入正确邮箱");
if (!f.code)
return this.toast("请输入验证码");
if (!f.password || f.password.length < 6)
return this.toast("密码至少6位");
if (f.password !== f.password2)
return this.toast("两次密码不一致");
this.loading = true;
try {
const data = await common_http.post("/api/auth/email/register", { name: f.name.trim(), email: f.email.trim(), code: f.code.trim(), password: f.password });
this.afterLogin(data);
} catch (e) {
this.toast(e.message);
} finally {
this.loading = false;
}
},
async sendResetCode() {
if (!this.validateEmail(this.resetForm.email))
return this.toast("请输入正确邮箱");
this.loading = true;
try {
const r = await common_http.post("/api/auth/email/send", { email: this.resetForm.email, scene: "reset" });
if (r && r.ok)
this.startCountdown("resetCountdown");
this.toast(r && r.ok ? "验证码已发送" : "发送过于频繁");
} catch (e) {
this.toast(e.message);
} finally {
this.loading = false;
}
},
async onReset() {
const f = this.resetForm;
if (!this.validateEmail(f.email))
return this.toast("请输入正确邮箱");
if (!f.code)
return this.toast("请输入验证码");
if (!f.password || f.password.length < 6)
return this.toast("新密码至少6位");
if (f.password !== f.password2)
return this.toast("两次密码不一致");
this.loading = true;
try {
const r = await common_http.post("/api/auth/email/reset-password", { email: f.email.trim(), code: f.code.trim(), newPassword: f.password, confirmPassword: f.password2 });
if (r && r.ok) {
this.toast("已重置,请使用新密码登录");
this.tab = "login";
this.loginForm.email = f.email;
} else
this.toast("重置失败");
} catch (e) {
this.toast(e.message);
} finally {
this.loading = false;
}
}
}
};
if (!Array) {
const _component_path = common_vendor.resolveComponent("path");
const _component_svg = common_vendor.resolveComponent("svg");
(_component_path + _component_svg)();
}
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
return {
a: common_vendor.p({
d: "M12 2C13.1 2 14 2.9 14 4C14 5.1 13.1 6 12 6C10.9 6 10 5.1 10 4C10 2.9 10.9 2 12 2ZM21 9V7L15 4V6C15 7.66 13.66 9 12 9S9 7.66 9 6V4L3 7V9C3 10.1 3.9 11 5 11V17C5 18.1 5.9 19 7 19H9C9 20.1 9.9 21 11 21H13C14.1 21 15 20.1 15 19H17C18.1 19 19 18.1 19 17V11C20.1 11 21 10.1 21 9Z"
}),
b: common_vendor.p({
viewBox: "0 0 24 24"
}),
c: common_vendor.p({
d: "M6.62,10.79C8.06,13.62 10.38,15.94 13.21,17.38L15.41,15.18C15.69,14.9 16.08,14.82 16.43,14.93C17.55,15.3 18.75,15.5 20,15.5A1,1 0 0,1 21,16.5V20A1,1 0 0,1 20,21A17,17 0 0,1 3,4A1,1 0 0,1 4,3H7.5A1,1 0 0,1 8.5,4C8.5,5.25 8.7,6.45 9.07,7.57C9.18,7.92 9.1,8.31 8.82,8.59L6.62,10.79Z"
}),
d: common_vendor.p({
viewBox: "0 0 24 24"
}),
e: common_vendor.o(($event) => $data.phoneFocused = true),
f: common_vendor.o(($event) => $data.phoneFocused = false),
g: $data.form.phone,
h: common_vendor.o(common_vendor.m(($event) => $data.form.phone = $event.detail.value, {
return common_vendor.e({
a: common_vendor.n($data.tab === "login" ? "active" : ""),
b: common_vendor.o(($event) => $data.tab = "login"),
c: common_vendor.n($data.tab === "register" ? "active" : ""),
d: common_vendor.o(($event) => $data.tab = "register"),
e: common_vendor.n($data.tab === "reset" ? "active" : ""),
f: common_vendor.o(($event) => $data.tab = "reset"),
g: $data.tab === "login"
}, $data.tab === "login" ? {
h: $data.loginForm.email,
i: common_vendor.o(common_vendor.m(($event) => $data.loginForm.email = $event.detail.value, {
trim: true
})),
i: $data.phoneFocused ? 1 : "",
j: $data.form.phone ? 1 : "",
k: common_vendor.p({
d: "M12,17A2,2 0 0,0 14,15C14,13.89 13.1,13 12,13A2,2 0 0,0 10,15A2,2 0 0,0 12,17M18,8A2,2 0 0,1 20,10V20A2,2 0 0,1 18,22H6A2,2 0 0,1 4,20V10C4,8.89 4.9,8 6,8H7V6A5,5 0 0,1 12,1A5,5 0 0,1 17,6V8H18M12,3A3,3 0 0,0 9,6V8H15V6A3,3 0 0,0 12,3Z"
}),
l: common_vendor.p({
viewBox: "0 0 24 24"
}),
m: common_vendor.o(($event) => $data.passwordFocused = true),
n: common_vendor.o(($event) => $data.passwordFocused = false),
o: $data.form.password,
p: common_vendor.o(common_vendor.m(($event) => $data.form.password = $event.detail.value, {
j: $data.loginForm.password,
k: common_vendor.o(($event) => $data.loginForm.password = $event.detail.value),
l: $data.loading,
m: common_vendor.o((...args) => $options.onLogin && $options.onLogin(...args))
} : $data.tab === "register" ? {
o: $data.regForm.name,
p: common_vendor.o(common_vendor.m(($event) => $data.regForm.name = $event.detail.value, {
trim: true
})),
q: $data.passwordFocused ? 1 : "",
r: $data.form.password ? 1 : "",
s: common_vendor.o((...args) => $options.onLogin && $options.onLogin(...args)),
t: common_vendor.o((...args) => $options.onGoRegister && $options.onGoRegister(...args))
};
q: $data.regForm.email,
r: common_vendor.o(common_vendor.m(($event) => $data.regForm.email = $event.detail.value, {
trim: true
})),
s: $data.regForm.code,
t: common_vendor.o(common_vendor.m(($event) => $data.regForm.code = $event.detail.value, {
trim: true
})),
v: common_vendor.t($data.regCountdown > 0 ? $data.regCountdown + "s" : "获取验证码"),
w: $data.regCountdown > 0 || $data.loading,
x: common_vendor.o((...args) => $options.sendRegCode && $options.sendRegCode(...args)),
y: $data.regForm.password,
z: common_vendor.o(($event) => $data.regForm.password = $event.detail.value),
A: $data.regForm.password2,
B: common_vendor.o(($event) => $data.regForm.password2 = $event.detail.value),
C: $data.loading,
D: common_vendor.o((...args) => $options.onRegister && $options.onRegister(...args))
} : {
E: $data.resetForm.email,
F: common_vendor.o(common_vendor.m(($event) => $data.resetForm.email = $event.detail.value, {
trim: true
})),
G: $data.resetForm.code,
H: common_vendor.o(common_vendor.m(($event) => $data.resetForm.code = $event.detail.value, {
trim: true
})),
I: common_vendor.t($data.resetCountdown > 0 ? $data.resetCountdown + "s" : "获取验证码"),
J: $data.resetCountdown > 0 || $data.loading,
K: common_vendor.o((...args) => $options.sendResetCode && $options.sendResetCode(...args)),
L: $data.resetForm.password,
M: common_vendor.o(($event) => $data.resetForm.password = $event.detail.value),
N: $data.resetForm.password2,
O: common_vendor.o(($event) => $data.resetForm.password2 = $event.detail.value),
P: $data.loading,
Q: common_vendor.o((...args) => $options.onReset && $options.onReset(...args))
}, {
n: $data.tab === "register"
});
}
const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render]]);
wx.createPage(MiniProgramPage);

View File

@@ -1 +1 @@
<view class="login-container"><view class="background-decoration"><view class="circle circle-1"></view><view class="circle circle-2"></view><view class="circle circle-3"></view></view><view class="login-card"><view class="header-section"><view class="logo-container"><view class="logo-icon"><svg wx:if="{{b}}" u-s="{{['d']}}" class="icon" u-i="5ce84e30-0" bind:__l="__l" u-p="{{b}}"><path wx:if="{{a}}" u-i="5ce84e30-1,5ce84e30-0" bind:__l="__l" u-p="{{a}}"/></svg></view><text class="app-name">配件询价</text></view><text class="welcome-text">欢迎回来</text><text class="subtitle">请登录您的账户</text></view><view class="form-section"><view class="input-group"><view class="{{['input-container', i && 'focused', j && 'filled']}}"><view class="input-icon"><svg wx:if="{{d}}" u-s="{{['d']}}" class="icon" u-i="5ce84e30-2" bind:__l="__l" u-p="{{d}}"><path wx:if="{{c}}" u-i="5ce84e30-3,5ce84e30-2" bind:__l="__l" u-p="{{c}}"/></svg></view><input class="input-field" type="number" placeholder="输入手机号" maxlength="11" bindfocus="{{e}}" bindblur="{{f}}" value="{{g}}" bindinput="{{h}}"/></view></view><view class="input-group"><view class="{{['input-container', q && 'focused', r && 'filled']}}"><view class="input-icon"><svg wx:if="{{l}}" u-s="{{['d']}}" class="icon" u-i="5ce84e30-4" bind:__l="__l" u-p="{{l}}"><path wx:if="{{k}}" u-i="5ce84e30-5,5ce84e30-4" bind:__l="__l" u-p="{{k}}"/></svg></view><input class="input-field" password placeholder="请输入密码" bindfocus="{{m}}" bindblur="{{n}}" value="{{o}}" bindinput="{{p}}"/></view></view></view><view class="actions-section"><button class="login-button" bindtap="{{s}}"><text class="button-text">登录</text></button><button class="register-button" bindtap="{{t}}"><text class="button-text">注册新账户</text></button></view><view class="footer-section"></view></view></view>
<view class="auth-page"><view class="tabs"><view class="{{['tab', a]}}" bindtap="{{b}}">登录</view><view class="{{['tab', c]}}" bindtap="{{d}}">注册</view><view class="{{['tab', e]}}" bindtap="{{f}}">忘记密码</view></view><view wx:if="{{g}}" class="panel"><input class="input" type="text" placeholder="输入邮箱" value="{{h}}" bindinput="{{i}}"/><input class="input" type="password" placeholder="输入密码" value="{{j}}" bindinput="{{k}}"/><button class="btn primary" disabled="{{l}}" bindtap="{{m}}">登录</button></view><view wx:elif="{{n}}" class="panel"><input class="input" type="text" placeholder="输入用户名" value="{{o}}" bindinput="{{p}}"/><input class="input" type="text" placeholder="输入邮箱" value="{{q}}" bindinput="{{r}}"/><view class="row"><input class="input flex1" type="text" placeholder="邮箱验证码" value="{{s}}" bindinput="{{t}}"/><button class="btn ghost" disabled="{{w}}" bindtap="{{x}}">{{v}}</button></view><input class="input" type="password" placeholder="输入密码(≥6位)" value="{{y}}" bindinput="{{z}}"/><input class="input" type="password" placeholder="再次输入密码" value="{{A}}" bindinput="{{B}}"/><button class="btn primary" disabled="{{C}}" bindtap="{{D}}">注册新用户</button></view><view wx:else class="panel"><input class="input" type="text" placeholder="输入邮箱" value="{{E}}" bindinput="{{F}}"/><view class="row"><input class="input flex1" type="text" placeholder="邮箱验证码" value="{{G}}" bindinput="{{H}}"/><button class="btn ghost" disabled="{{J}}" bindtap="{{K}}">{{I}}</button></view><input class="input" type="password" placeholder="新密码(≥6位)" value="{{L}}" bindinput="{{M}}"/><input class="input" type="password" placeholder="再次输入新密码" value="{{N}}" bindinput="{{O}}"/><button class="btn primary" disabled="{{P}}" bindtap="{{Q}}">重置密码</button></view></view>

View File

@@ -24,257 +24,62 @@
/* 垂直间距 */
/* 透明度 */
/* 文章场景相关 */
.login-container {
position: relative;
min-height: 100vh;
.auth-page {
padding: 32rpx;
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
padding: 40rpx 20rpx;
overflow: hidden;
flex-direction: column;
gap: 24rpx;
}
.background-decoration {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 0;
}
.background-decoration .circle {
position: absolute;
border-radius: 50%;
background: rgba(255, 255, 255, 0.1);
}
.background-decoration .circle.circle-1 {
width: 200rpx;
height: 200rpx;
top: 10%;
left: 10%;
animation: float 6s ease-in-out infinite;
}
.background-decoration .circle.circle-2 {
width: 150rpx;
height: 150rpx;
top: 60%;
right: 15%;
animation: float 8s ease-in-out infinite reverse;
}
.background-decoration .circle.circle-3 {
width: 100rpx;
height: 100rpx;
bottom: 20%;
left: 20%;
animation: float 5s ease-in-out infinite;
}
@keyframes float {
0%, 100% {
transform: translateY(0px);
}
50% {
transform: translateY(-20px);
}
}
.login-card {
position: relative;
z-index: 1;
width: 90%;
max-width: 680rpx;
background: rgba(255, 255, 255, 0.95);
-webkit-backdrop-filter: blur(20rpx);
backdrop-filter: blur(20rpx);
border-radius: 32rpx;
padding: 60rpx 40rpx 50rpx;
box-shadow: 0 20rpx 60rpx rgba(0, 0, 0, 0.1);
border: 1rpx solid rgba(255, 255, 255, 0.2);
}
.header-section {
text-align: center;
margin-bottom: 50rpx;
}
.header-section .logo-container {
.tabs {
display: flex;
align-items: center;
justify-content: center;
gap: 24rpx;
}
.tab {
padding: 12rpx 20rpx;
border-radius: 999rpx;
background: #f2f4f8;
color: #5b6b80;
font-weight: 700;
}
.tab.active {
background: #2d6be6;
color: #fff;
}
.panel {
display: flex;
flex-direction: column;
gap: 16rpx;
margin-bottom: 20rpx;
}
.header-section .logo-container .logo-icon {
width: 60rpx;
height: 60rpx;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
background: #fff;
padding: 24rpx;
border-radius: 16rpx;
display: flex;
align-items: center;
justify-content: center;
border: 2rpx solid #eef2f9;
}
.header-section .logo-container .logo-icon .icon {
width: 36rpx;
height: 36rpx;
fill: white;
}
.header-section .logo-container .app-name {
font-size: 36rpx;
font-weight: 700;
color: #2d3748;
letter-spacing: 1rpx;
}
.header-section .welcome-text {
display: block;
font-size: 48rpx;
font-weight: 700;
color: #2d3748;
margin-bottom: 8rpx;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.header-section .subtitle {
display: block;
.input {
background: #f7f9ff;
border: 2rpx solid rgba(45, 107, 230, 0.12);
border-radius: 12rpx;
padding: 22rpx 20rpx;
font-size: 28rpx;
color: #718096;
font-weight: 400;
}
.form-section {
margin-bottom: 40rpx;
}
.form-section .input-group {
margin-bottom: 28rpx;
}
.form-section .input-group .input-container {
position: relative;
background: #f7fafc;
border: 2rpx solid #e2e8f0;
border-radius: 16rpx;
.row {
display: flex;
gap: 12rpx;
align-items: center;
transition: all 0.3s ease;
}
.form-section .input-group .input-container.focused {
border-color: #667eea;
background: #ffffff;
box-shadow: 0 0 0 6rpx rgba(102, 126, 234, 0.1);
transform: translateY(-2rpx);
}
.form-section .input-group .input-container.filled {
background: #ffffff;
border-color: #cbd5e0;
}
.form-section .input-group .input-container .input-icon {
display: flex;
align-items: center;
justify-content: center;
width: 50rpx;
margin-left: 20rpx;
}
.form-section .input-group .input-container .input-icon .icon {
width: 32rpx;
height: 32rpx;
fill: #a0aec0;
transition: fill 0.3s ease;
}
.form-section .input-group .input-container.focused .input-icon .icon {
fill: #667eea;
}
.form-section .input-group .input-container .input-field {
.flex1 {
flex: 1;
background: transparent;
border: none;
padding: 24rpx 20rpx 24rpx 12rpx;
font-size: 32rpx;
color: #2d3748;
}
.form-section .input-group .input-container .input-field::-webkit-input-placeholder {
color: #a0aec0;
font-size: 28rpx;
}
.form-section .input-group .input-container .input-field::placeholder {
color: #a0aec0;
font-size: 28rpx;
}
.actions-section {
margin-bottom: 30rpx;
}
.actions-section .login-button {
width: 100%;
height: 96rpx;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border: none;
border-radius: 16rpx;
margin-bottom: 20rpx;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 8rpx 24rpx rgba(102, 126, 234, 0.3);
transition: all 0.3s ease;
position: relative;
overflow: hidden;
}
.actions-section .login-button:active {
transform: translateY(2rpx);
box-shadow: 0 4rpx 16rpx rgba(102, 126, 234, 0.3);
}
.actions-section .login-button::before {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(135deg, rgba(255, 255, 255, 0.2) 0%, transparent 50%);
opacity: 0;
transition: opacity 0.3s ease;
}
.actions-section .login-button:active::before {
opacity: 1;
}
.actions-section .login-button .button-text {
font-size: 32rpx;
font-weight: 600;
color: white;
letter-spacing: 1rpx;
}
.actions-section .register-button {
width: 100%;
height: 86rpx;
background: transparent;
border: 2rpx solid #e2e8f0;
border-radius: 16rpx;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
}
.actions-section .register-button:active {
background: #f7fafc;
border-color: #cbd5e0;
transform: translateY(1rpx);
}
.actions-section .register-button .button-text {
font-size: 28rpx;
font-weight: 500;
color: #718096;
}
.footer-section {
.btn {
padding: 22rpx 20rpx;
border-radius: 12rpx;
font-weight: 800;
text-align: center;
}
.footer-section .hint-text {
font-size: 24rpx;
color: #a0aec0;
line-height: 1.5;
background: rgba(160, 174, 192, 0.1);
padding: 16rpx 20rpx;
border-radius: 12rpx;
border: 1rpx solid rgba(160, 174, 192, 0.2);
}
@media (max-width: 750rpx) {
.login-card {
margin: 20rpx;
padding: 50rpx 30rpx 40rpx;
}
.header-section .welcome-text {
font-size: 42rpx;
.btn.primary {
background: linear-gradient(135deg, #4788ff 0%, #2d6be6 100%);
color: #fff;
}
.btn.ghost {
background: #eef3ff;
color: #2d6be6;
}

View File

@@ -7,55 +7,97 @@ const _sfc_main = {
form: {
shopName: "",
name: "",
phone: "",
password: "",
confirmPassword: ""
email: "",
code: "",
password: ""
},
shopNameFocused: false,
nameFocused: false,
phoneFocused: false,
passwordFocused: false,
confirmPasswordFocused: false
emailFocused: false,
codeFocused: false,
pwdFocused: false,
countdown: 0,
timer: null,
sending: false
};
},
computed: {
btnText() {
if (this.countdown > 0)
return `${this.countdown}s`;
if (this.sending)
return "发送中...";
return "获取验证码";
}
},
methods: {
validate() {
const phone = String(this.form.phone || "").trim();
const ok = /^1[3-9]\d{9}$/.test(phone);
const email = String(this.form.email || "").trim();
const ok = /^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$/.test(email);
if (!ok) {
common_vendor.index.showToast({ title: "请输入正确的手机号", icon: "none" });
common_vendor.index.showToast({ title: "请输入正确的邮箱地址", icon: "none" });
return false;
}
if (!this.form.password) {
common_vendor.index.showToast({ title: "请输入密码", icon: "none" });
if (!/^\d{6}$/.test(String(this.form.code || "").trim())) {
common_vendor.index.showToast({ title: "验证码格式不正确", icon: "none" });
return false;
}
if (this.form.password.length < 6) {
if (String(this.form.password || "").length < 6) {
common_vendor.index.showToast({ title: "密码至少6位", icon: "none" });
return false;
}
if (!this.form.confirmPassword) {
common_vendor.index.showToast({ title: "请确认密码", icon: "none" });
return false;
}
if (this.form.password !== this.form.confirmPassword) {
common_vendor.index.showToast({ title: "两次密码不一致", icon: "none" });
return false;
}
return true;
},
startCountdown(sec) {
this.countdown = sec;
if (this.timer)
clearInterval(this.timer);
this.timer = setInterval(() => {
if (this.countdown <= 1) {
clearInterval(this.timer);
this.timer = null;
this.countdown = 0;
return;
}
this.countdown--;
}, 1e3);
},
async sendCode() {
if (this.sending || this.countdown > 0)
return;
const e = String(this.form.email || "").trim();
const ok = /^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$/.test(e);
if (!ok)
return common_vendor.index.showToast({ title: "请输入正确的邮箱地址", icon: "none" });
this.sending = true;
try {
const res = await common_http.post("/api/auth/email/send", { email: e, scene: "login" });
const cd = Number(res && res.cooldownSec || 60);
this.startCountdown(cd);
common_vendor.index.showToast({ title: "验证码已发送", icon: "none" });
} catch (e2) {
const msg = e2 && e2.message || "发送失败";
common_vendor.index.showToast({ title: msg, icon: "none" });
} finally {
this.sending = false;
}
},
async onRegister() {
if (!this.validate())
return;
const phone = String(this.form.phone || "").trim();
const email = String(this.form.email || "").trim();
const name = String(this.form.name || "").trim();
const password = String(this.form.password || "");
try {
const data = await common_http.post("/api/auth/register", { phone, name: name || void 0, password });
const data = await common_http.post("/api/auth/email/register", { email, code: String(this.form.code || "").trim(), name, password: String(this.form.password || "") });
if (data && data.token) {
common_vendor.index.setStorageSync("TOKEN", data.token);
if (data.user && data.user.phone)
common_vendor.index.setStorageSync("USER_MOBILE", data.user.phone);
if (data.user && data.user.email)
common_vendor.index.setStorageSync("USER_EMAIL", data.user.email);
if (name)
try {
common_vendor.index.setStorageSync("USER_NAME", name);
} catch (_) {
}
common_vendor.index.showToast({ title: "注册成功", icon: "none" });
setTimeout(() => {
common_vendor.index.reLaunch({ url: "/pages/index/index" });
@@ -115,49 +157,52 @@ function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
q: $data.nameFocused ? 1 : "",
r: $data.form.name ? 1 : "",
s: common_vendor.p({
d: "M6.62,10.79C8.06,13.62 10.38,15.94 13.21,17.38L15.41,15.18C15.69,14.9 16.08,14.82 16.43,14.93C17.55,15.3 18.75,15.5 20,15.5A1,1 0 0,1 21,16.5V20A1,1 0 0,1 20,21A17,17 0 0,1 3,4A1,1 0 0,1 4,3H7.5A1,1 0 0,1 8.5,4C8.5,5.25 8.7,6.45 9.07,7.57C9.18,7.92 9.1,8.31 8.82,8.59L6.62,10.79Z"
d: "M20 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm-1 4l-7 4-7-4V6l7 4 7-4v2z"
}),
t: common_vendor.p({
viewBox: "0 0 24 24"
}),
v: common_vendor.o(($event) => $data.phoneFocused = true),
w: common_vendor.o(($event) => $data.phoneFocused = false),
x: $data.form.phone,
y: common_vendor.o(common_vendor.m(($event) => $data.form.phone = $event.detail.value, {
v: common_vendor.o(($event) => $data.emailFocused = true),
w: common_vendor.o(($event) => $data.emailFocused = false),
x: $data.form.email,
y: common_vendor.o(common_vendor.m(($event) => $data.form.email = $event.detail.value, {
trim: true
})),
z: $data.phoneFocused ? 1 : "",
A: $data.form.phone ? 1 : "",
z: $data.emailFocused ? 1 : "",
A: $data.form.email ? 1 : "",
B: common_vendor.p({
d: "M12,17A2,2 0 0,0 14,15C14,13.89 13.1,13 12,13A2,2 0 0,0 10,15A2,2 0 0,0 12,17M18,8A2,2 0 0,1 20,10V20A2,2 0 0,1 18,22H6A2,2 0 0,1 4,20V10C4,8.89 4.9,8 6,8H7V6A5,5 0 0,1 12,1A5,5 0 0,1 17,6V8H18M12,3A3,3 0 0,0 9,6V8H15V6A3,3 0 0,0 12,3Z"
d: "M3 10h18v2H3v-2zm0 6h12v2H3v-2zM3 6h18v2H3V6z"
}),
C: common_vendor.p({
viewBox: "0 0 24 24"
}),
D: common_vendor.o(($event) => $data.passwordFocused = true),
E: common_vendor.o(($event) => $data.passwordFocused = false),
F: $data.form.password,
G: common_vendor.o(common_vendor.m(($event) => $data.form.password = $event.detail.value, {
D: common_vendor.o(($event) => $data.codeFocused = true),
E: common_vendor.o(($event) => $data.codeFocused = false),
F: $data.form.code,
G: common_vendor.o(common_vendor.m(($event) => $data.form.code = $event.detail.value, {
trim: true
})),
H: $data.passwordFocused ? 1 : "",
I: $data.form.password ? 1 : "",
H: $data.codeFocused ? 1 : "",
I: $data.form.code ? 1 : "",
J: common_vendor.p({
d: "M12,17A2,2 0 0,0 14,15C14,13.89 13.1,13 12,13A2,2 0 0,0 10,15A2,2 0 0,0 12,17M18,8A2,2 0 0,1 20,10V20A2,2 0 0,1 18,22H6A2,2 0 0,1 4,20V10C4,8.89 4.9,8 6,8H7V6A5,5 0 0,1 12,1A5,5 0 0,1 17,6V8H18M12,3A3,3 0 0,0 9,6V8H15V6A3,3 0 0,0 12,3Z"
}),
K: common_vendor.p({
viewBox: "0 0 24 24"
}),
L: common_vendor.o(($event) => $data.confirmPasswordFocused = true),
M: common_vendor.o(($event) => $data.confirmPasswordFocused = false),
N: $data.form.confirmPassword,
O: common_vendor.o(common_vendor.m(($event) => $data.form.confirmPassword = $event.detail.value, {
L: common_vendor.o(($event) => $data.pwdFocused = true),
M: common_vendor.o(($event) => $data.pwdFocused = false),
N: $data.form.password,
O: common_vendor.o(common_vendor.m(($event) => $data.form.password = $event.detail.value, {
trim: true
})),
P: $data.confirmPasswordFocused ? 1 : "",
Q: $data.form.confirmPassword ? 1 : "",
R: common_vendor.o((...args) => $options.onRegister && $options.onRegister(...args)),
S: common_vendor.o((...args) => $options.onGoLogin && $options.onGoLogin(...args))
P: $data.pwdFocused ? 1 : "",
Q: $data.form.password ? 1 : "",
R: common_vendor.t($options.btnText),
S: $data.countdown > 0 || $data.sending,
T: common_vendor.o((...args) => $options.sendCode && $options.sendCode(...args)),
U: common_vendor.o((...args) => $options.onRegister && $options.onRegister(...args)),
V: common_vendor.o((...args) => $options.onGoLogin && $options.onGoLogin(...args))
};
}
const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render]]);

View File

@@ -1 +1 @@
<view class="register-container"><view class="background-decoration"><view class="circle circle-1"></view><view class="circle circle-2"></view><view class="circle circle-3"></view></view><view class="register-card"><view class="header-section"><view class="logo-container"><view class="logo-icon"><svg wx:if="{{b}}" u-s="{{['d']}}" class="icon" u-i="41009218-0" bind:__l="__l" u-p="{{b}}"><path wx:if="{{a}}" u-i="41009218-1,41009218-0" bind:__l="__l" u-p="{{a}}"/></svg></view><text class="app-name">配件询价</text></view><text class="welcome-text">创建账户</text><text class="subtitle">请填写以下信息完成注册</text></view><view class="form-section"><view class="input-group"><view class="{{['input-container', i && 'focused', j && 'filled']}}"><view class="input-icon"><svg wx:if="{{d}}" u-s="{{['d']}}" class="icon" u-i="41009218-2" bind:__l="__l" u-p="{{d}}"><path wx:if="{{c}}" u-i="41009218-3,41009218-2" bind:__l="__l" u-p="{{c}}"/></svg></view><input class="input-field" type="text" placeholder="请输入店铺名称" bindfocus="{{e}}" bindblur="{{f}}" value="{{g}}" bindinput="{{h}}"/></view></view><view class="input-group"><view class="{{['input-container', q && 'focused', r && 'filled']}}"><view class="input-icon"><svg wx:if="{{l}}" u-s="{{['d']}}" class="icon" u-i="41009218-4" bind:__l="__l" u-p="{{l}}"><path wx:if="{{k}}" u-i="41009218-5,41009218-4" bind:__l="__l" u-p="{{k}}"/></svg></view><input class="input-field" type="text" placeholder="请输入您的姓名" bindfocus="{{m}}" bindblur="{{n}}" value="{{o}}" bindinput="{{p}}"/></view></view><view class="input-group"><view class="{{['input-container', z && 'focused', A && 'filled']}}"><view class="input-icon"><svg wx:if="{{t}}" u-s="{{['d']}}" class="icon" u-i="41009218-6" bind:__l="__l" u-p="{{t}}"><path wx:if="{{s}}" u-i="41009218-7,41009218-6" bind:__l="__l" u-p="{{s}}"/></svg></view><input class="input-field" type="number" placeholder="请输入手机号" maxlength="11" bindfocus="{{v}}" bindblur="{{w}}" value="{{x}}" bindinput="{{y}}"/></view></view><view class="input-group"><view class="{{['input-container', H && 'focused', I && 'filled']}}"><view class="input-icon"><svg wx:if="{{C}}" u-s="{{['d']}}" class="icon" u-i="41009218-8" bind:__l="__l" u-p="{{C}}"><path wx:if="{{B}}" u-i="41009218-9,41009218-8" bind:__l="__l" u-p="{{B}}"/></svg></view><input class="input-field" password placeholder="请输入密码至少6位" bindfocus="{{D}}" bindblur="{{E}}" value="{{F}}" bindinput="{{G}}"/></view></view><view class="input-group"><view class="{{['input-container', P && 'focused', Q && 'filled']}}"><view class="input-icon"><svg wx:if="{{K}}" u-s="{{['d']}}" class="icon" u-i="41009218-10" bind:__l="__l" u-p="{{K}}"><path wx:if="{{J}}" u-i="41009218-11,41009218-10" bind:__l="__l" u-p="{{J}}"/></svg></view><input class="input-field" password placeholder="请再次输入密码" bindfocus="{{L}}" bindblur="{{M}}" value="{{N}}" bindinput="{{O}}"/></view></view></view><view class="actions-section"><button class="register-button" bindtap="{{R}}"><text class="button-text">立即注册</text></button><button class="login-button" bindtap="{{S}}"><text class="button-text">已有账户?去登录</text></button></view><view class="footer-section"><text class="hint-text">注册即表示您同意我们的服务条款和隐私政策</text></view></view></view>
<view class="register-container"><view class="background-decoration"><view class="circle circle-1"></view><view class="circle circle-2"></view><view class="circle circle-3"></view></view><view class="register-card"><view class="header-section"><view class="logo-container"><view class="logo-icon"><svg wx:if="{{b}}" u-s="{{['d']}}" class="icon" u-i="41009218-0" bind:__l="__l" u-p="{{b}}"><path wx:if="{{a}}" u-i="41009218-1,41009218-0" bind:__l="__l" u-p="{{a}}"/></svg></view><text class="app-name">配件询价</text></view><text class="welcome-text">创建账户</text><text class="subtitle">请填写以下信息完成注册</text></view><view class="form-section"><view class="input-group"><view class="{{['input-container', i && 'focused', j && 'filled']}}"><view class="input-icon"><svg wx:if="{{d}}" u-s="{{['d']}}" class="icon" u-i="41009218-2" bind:__l="__l" u-p="{{d}}"><path wx:if="{{c}}" u-i="41009218-3,41009218-2" bind:__l="__l" u-p="{{c}}"/></svg></view><input class="input-field" type="text" placeholder="请输入店铺名称" bindfocus="{{e}}" bindblur="{{f}}" value="{{g}}" bindinput="{{h}}"/></view></view><view class="input-group"><view class="{{['input-container', q && 'focused', r && 'filled']}}"><view class="input-icon"><svg wx:if="{{l}}" u-s="{{['d']}}" class="icon" u-i="41009218-4" bind:__l="__l" u-p="{{l}}"><path wx:if="{{k}}" u-i="41009218-5,41009218-4" bind:__l="__l" u-p="{{k}}"/></svg></view><input class="input-field" type="text" placeholder="请输入您的姓名" bindfocus="{{m}}" bindblur="{{n}}" value="{{o}}" bindinput="{{p}}"/></view></view><view class="input-group"><view class="{{['input-container', z && 'focused', A && 'filled']}}"><view class="input-icon"><svg wx:if="{{t}}" u-s="{{['d']}}" class="icon" u-i="41009218-6" bind:__l="__l" u-p="{{t}}"><path wx:if="{{s}}" u-i="41009218-7,41009218-6" bind:__l="__l" u-p="{{s}}"/></svg></view><input class="input-field" type="text" placeholder="请输入邮箱地址" bindfocus="{{v}}" bindblur="{{w}}" value="{{x}}" bindinput="{{y}}"/></view></view><view class="input-group"><view class="{{['input-container', H && 'focused', I && 'filled']}}"><view class="input-icon"><svg wx:if="{{C}}" u-s="{{['d']}}" class="icon" u-i="41009218-8" bind:__l="__l" u-p="{{C}}"><path wx:if="{{B}}" u-i="41009218-9,41009218-8" bind:__l="__l" u-p="{{B}}"/></svg></view><input class="input-field" type="number" maxlength="6" placeholder="请输入6位验证码" bindfocus="{{D}}" bindblur="{{E}}" value="{{F}}" bindinput="{{G}}"/></view></view><view class="input-group"><view class="{{['input-container', P && 'focused', Q && 'filled']}}"><view class="input-icon"><svg wx:if="{{K}}" u-s="{{['d']}}" class="icon" u-i="41009218-10" bind:__l="__l" u-p="{{K}}"><path wx:if="{{J}}" u-i="41009218-11,41009218-10" bind:__l="__l" u-p="{{J}}"/></svg></view><input class="input-field" password placeholder="请设置登录密码至少6位" bindfocus="{{L}}" bindblur="{{M}}" value="{{N}}" bindinput="{{O}}"/></view></view><view class="input-group"><button class="login-button" disabled="{{S}}" bindtap="{{T}}">{{R}}</button></view></view><view class="actions-section"><button class="register-button" bindtap="{{U}}"><text class="button-text">立即注册</text></button><button class="login-button" bindtap="{{V}}"><text class="button-text">已有账户?去登录</text></button></view><view class="footer-section"><text class="hint-text">注册即表示您同意我们的服务条款和隐私政策</text></view></view></view>

View File

@@ -3,7 +3,7 @@ const common_vendor = require("../../common/vendor.js");
const common_http = require("../../common/http.js");
const _sfc_main = {
data() {
return { id: null, d: {}, editing: false, form: { name: "", contactName: "", mobile: "", phone: "", address: "", level: "", priceLevel: "零售价", arOpening: 0, remark: "" }, priceLevels: ["零售价", "批发价", "大单报价"], priceLabels: ["零售价", "批发价", "大单报价"], priceIdx: 0 };
return { id: null, d: {}, editing: false, form: { name: "", contactName: "", mobile: "", phone: "", address: "", priceLevel: "零售价", arOpening: 0, remark: "" }, priceLevels: ["零售价", "批发价", "大单报价"], priceLabels: ["零售价", "批发价", "大单报价"], priceIdx: 0 };
},
onLoad(q) {
if (q && q.id) {
@@ -21,7 +21,6 @@ const _sfc_main = {
mobile: this.d.mobile || "",
phone: this.d.phone || "",
address: this.d.address || "",
level: this.d.level || "",
priceLevel: this.d.priceLevel || "retail",
arOpening: Number(this.d.arOpening || 0),
remark: this.d.remark || ""
@@ -111,44 +110,37 @@ function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
}, {
v: !$data.editing
}, !$data.editing ? {
w: common_vendor.t($data.d.level || "—")
w: common_vendor.t($data.d.priceLevel)
} : {
x: $data.form.level,
y: common_vendor.o(($event) => $data.form.level = $event.detail.value)
x: common_vendor.t($data.priceLabels[$data.priceIdx]),
y: $data.priceLabels,
z: $data.priceIdx,
A: common_vendor.o((...args) => $options.onPriceChange && $options.onPriceChange(...args))
}, {
z: !$data.editing
B: !$data.editing
}, !$data.editing ? {
A: common_vendor.t($data.d.priceLevel)
C: common_vendor.t(Number($data.d.arOpening || 0).toFixed(2))
} : {
B: common_vendor.t($data.priceLabels[$data.priceIdx]),
C: $data.priceLabels,
D: $data.priceIdx,
E: common_vendor.o((...args) => $options.onPriceChange && $options.onPriceChange(...args))
}, {
F: !$data.editing
}, !$data.editing ? {
G: common_vendor.t(Number($data.d.arOpening || 0).toFixed(2))
} : {
H: $data.form.arOpening,
I: common_vendor.o(common_vendor.m(($event) => $data.form.arOpening = $event.detail.value, {
D: $data.form.arOpening,
E: common_vendor.o(common_vendor.m(($event) => $data.form.arOpening = $event.detail.value, {
number: true
}))
}, {
J: common_vendor.t(Number($data.d.receivable || 0).toFixed(2)),
K: !$data.editing
F: common_vendor.t(Number($data.d.receivable || 0).toFixed(2)),
G: !$data.editing
}, !$data.editing ? {
L: common_vendor.t($data.d.remark || "—")
H: common_vendor.t($data.d.remark || "—")
} : {
M: $data.form.remark,
N: common_vendor.o(($event) => $data.form.remark = $event.detail.value)
I: $data.form.remark,
J: common_vendor.o(($event) => $data.form.remark = $event.detail.value)
}, {
O: common_vendor.t($data.editing ? "取消" : "编辑"),
P: common_vendor.o((...args) => $options.toggleEdit && $options.toggleEdit(...args)),
Q: $data.editing
K: common_vendor.t($data.editing ? "取消" : "编辑"),
L: common_vendor.o((...args) => $options.toggleEdit && $options.toggleEdit(...args)),
M: $data.editing
}, $data.editing ? {
R: common_vendor.o((...args) => $options.save && $options.save(...args))
N: common_vendor.o((...args) => $options.save && $options.save(...args))
} : {
S: common_vendor.o((...args) => $options.choose && $options.choose(...args))
O: common_vendor.o((...args) => $options.choose && $options.choose(...args))
});
}
const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render]]);

View File

@@ -1 +1 @@
<view class="page"><view class="card"><view class="row"><text class="label">名称</text><text wx:if="{{a}}" class="value">{{b}}</text><input wx:else class="value-input" placeholder="必填" value="{{c}}" bindinput="{{d}}"/></view><view class="row"><text class="label">联系人</text><text wx:if="{{e}}" class="value">{{f}}</text><input wx:else class="value-input" placeholder="可选" value="{{g}}" bindinput="{{h}}"/></view><view class="row"><text class="label">手机</text><text wx:if="{{i}}" class="value">{{j}}</text><input wx:else class="value-input" placeholder="可选" value="{{k}}" bindinput="{{l}}"/></view><view class="row"><text class="label">电话</text><text wx:if="{{m}}" class="value">{{n}}</text><input wx:else class="value-input" placeholder="可选(座机)" value="{{o}}" bindinput="{{p}}"/></view><view class="row"><text class="label">地址</text><text wx:if="{{q}}" class="value">{{r}}</text><input wx:else class="value-input" placeholder="可选" value="{{s}}" bindinput="{{t}}"/></view><view class="row"><text class="label">等级</text><text wx:if="{{v}}" class="value">{{w}}</text><input wx:else class="value-input" placeholder="可选,如 VIP/A/B" value="{{x}}" bindinput="{{y}}"/></view><view class="row"><text class="label">售价档位</text><text wx:if="{{z}}" class="value">{{A}}</text><picker wx:else range="{{C}}" value="{{D}}" bindchange="{{E}}"><view class="value">{{B}}</view></picker></view><view class="row"><text class="label">初始应收</text><text wx:if="{{F}}" class="value">¥ {{G}}</text><input wx:else class="value-input" type="digit" placeholder="0.00" value="{{H}}" bindinput="{{I}}"/></view><view class="row"><text class="label">当前应收</text><text class="value emp">¥ {{J}}</text></view><view class="row"><text class="label">备注</text><text wx:if="{{K}}" class="value">{{L}}</text><input wx:else class="value-input" placeholder="—" value="{{M}}" bindinput="{{N}}"/></view></view><view class="bottom"><button class="ghost" bindtap="{{P}}">{{O}}</button><button wx:if="{{Q}}" class="primary" bindtap="{{R}}">保存</button><button wx:else class="primary" bindtap="{{S}}">选择此客户</button></view></view>
<view class="page"><view class="card"><view class="row"><text class="label">名称</text><text wx:if="{{a}}" class="value">{{b}}</text><input wx:else class="value-input" placeholder="必填" value="{{c}}" bindinput="{{d}}"/></view><view class="row"><text class="label">联系人</text><text wx:if="{{e}}" class="value">{{f}}</text><input wx:else class="value-input" placeholder="可选" value="{{g}}" bindinput="{{h}}"/></view><view class="row"><text class="label">手机</text><text wx:if="{{i}}" class="value">{{j}}</text><input wx:else class="value-input" placeholder="可选" value="{{k}}" bindinput="{{l}}"/></view><view class="row"><text class="label">电话</text><text wx:if="{{m}}" class="value">{{n}}</text><input wx:else class="value-input" placeholder="可选(座机)" value="{{o}}" bindinput="{{p}}"/></view><view class="row"><text class="label">地址</text><text wx:if="{{q}}" class="value">{{r}}</text><input wx:else class="value-input" placeholder="可选" value="{{s}}" bindinput="{{t}}"/></view><view class="row"><text class="label">售价档位</text><text wx:if="{{v}}" class="value">{{w}}</text><picker wx:else range="{{y}}" value="{{z}}" bindchange="{{A}}"><view class="value">{{x}}</view></picker></view><view class="row"><text class="label">初始应收</text><text wx:if="{{B}}" class="value">¥ {{C}}</text><input wx:else class="value-input" type="digit" placeholder="0.00" value="{{D}}" bindinput="{{E}}"/></view><view class="row"><text class="label">当前应收</text><text class="value emp">¥ {{F}}</text></view><view class="row"><text class="label">备注</text><text wx:if="{{G}}" class="value">{{H}}</text><input wx:else class="value-input" placeholder="—" value="{{I}}" bindinput="{{J}}"/></view></view><view class="bottom"><button class="ghost" bindtap="{{L}}">{{K}}</button><button wx:if="{{M}}" class="primary" bindtap="{{N}}">保存</button><button wx:else class="primary" bindtap="{{O}}">选择此客户</button></view></view>

View File

@@ -5,7 +5,7 @@ const _sfc_main = {
data() {
return {
id: null,
form: { name: "", level: "", priceLevel: "retail", contactName: "", mobile: "", phone: "", address: "", arOpening: 0, remark: "" },
form: { name: "", priceLevel: "retail", contactName: "", mobile: "", phone: "", address: "", arOpening: 0, remark: "" },
priceLevels: ["零售价", "批发价", "大单报价"],
priceLabels: ["零售价", "批发价", "大单报价"],
priceIdx: 0
@@ -14,6 +14,7 @@ const _sfc_main = {
onLoad(query) {
if (query && query.id) {
this.id = Number(query.id);
this.load();
}
},
methods: {
@@ -21,6 +22,27 @@ const _sfc_main = {
this.priceIdx = Number(e.detail.value);
this.form.priceLevel = this.priceLevels[this.priceIdx];
},
async load() {
if (!this.id)
return;
try {
const d = await common_http.get(`/api/customers/${this.id}`);
this.form = {
name: (d == null ? void 0 : d.name) || "",
priceLevel: (d == null ? void 0 : d.priceLevel) || "零售价",
contactName: (d == null ? void 0 : d.contactName) || "",
mobile: (d == null ? void 0 : d.mobile) || "",
phone: (d == null ? void 0 : d.phone) || "",
address: (d == null ? void 0 : d.address) || "",
arOpening: Number((d == null ? void 0 : d.arOpening) || 0),
remark: (d == null ? void 0 : d.remark) || ""
};
const idx = this.priceLevels.indexOf(this.form.priceLevel || "零售价");
this.priceIdx = idx >= 0 ? idx : 0;
} catch (e) {
common_vendor.index.showToast({ title: (e == null ? void 0 : e.message) || "加载失败", icon: "none" });
}
},
async save() {
if (!this.form.name)
return common_vendor.index.showToast({ title: "请填写客户名称", icon: "none" });
@@ -41,27 +63,25 @@ function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
return {
a: $data.form.name,
b: common_vendor.o(($event) => $data.form.name = $event.detail.value),
c: $data.form.level,
d: common_vendor.o(($event) => $data.form.level = $event.detail.value),
e: common_vendor.t($data.priceLabels[$data.priceIdx]),
f: $data.priceLabels,
g: $data.priceIdx,
h: common_vendor.o((...args) => $options.onPriceChange && $options.onPriceChange(...args)),
i: $data.form.contactName,
j: common_vendor.o(($event) => $data.form.contactName = $event.detail.value),
k: $data.form.mobile,
l: common_vendor.o(($event) => $data.form.mobile = $event.detail.value),
m: $data.form.phone,
n: common_vendor.o(($event) => $data.form.phone = $event.detail.value),
o: $data.form.address,
p: common_vendor.o(($event) => $data.form.address = $event.detail.value),
q: $data.form.arOpening,
r: common_vendor.o(common_vendor.m(($event) => $data.form.arOpening = $event.detail.value, {
c: common_vendor.t($data.priceLabels[$data.priceIdx]),
d: $data.priceLabels,
e: $data.priceIdx,
f: common_vendor.o((...args) => $options.onPriceChange && $options.onPriceChange(...args)),
g: $data.form.contactName,
h: common_vendor.o(($event) => $data.form.contactName = $event.detail.value),
i: $data.form.mobile,
j: common_vendor.o(($event) => $data.form.mobile = $event.detail.value),
k: $data.form.phone,
l: common_vendor.o(($event) => $data.form.phone = $event.detail.value),
m: $data.form.address,
n: common_vendor.o(($event) => $data.form.address = $event.detail.value),
o: $data.form.arOpening,
p: common_vendor.o(common_vendor.m(($event) => $data.form.arOpening = $event.detail.value, {
number: true
})),
s: $data.form.remark,
t: common_vendor.o(($event) => $data.form.remark = $event.detail.value),
v: common_vendor.o((...args) => $options.save && $options.save(...args))
q: $data.form.remark,
r: common_vendor.o(($event) => $data.form.remark = $event.detail.value),
s: common_vendor.o((...args) => $options.save && $options.save(...args))
};
}
const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render]]);

View File

@@ -1 +1 @@
<view class="page"><view class="field"><text class="label">客户名称</text><input class="value" placeholder="必填" value="{{a}}" bindinput="{{b}}"/></view><view class="field"><text class="label">客户等级</text><input class="value" placeholder="可选,如 VIP/A/B" value="{{c}}" bindinput="{{d}}"/></view><view class="field"><text class="label">售价档位</text><picker range="{{f}}" value="{{g}}" bindchange="{{h}}"><view class="value">{{e}}</view></picker></view><view class="field"><text class="label">联系人</text><input class="value" placeholder="可选" value="{{i}}" bindinput="{{j}}"/></view><view class="field"><text class="label">手机</text><input class="value" placeholder="可选" value="{{k}}" bindinput="{{l}}"/></view><view class="field"><text class="label">电话</text><input class="value" placeholder="可选(座机)" value="{{m}}" bindinput="{{n}}"/></view><view class="field"><text class="label">送货地址</text><input class="value" placeholder="可选" value="{{o}}" bindinput="{{p}}"/></view><view class="field"><text class="label">初始应收</text><input class="value" type="digit" placeholder="默认 0.00" value="{{q}}" bindinput="{{r}}"/></view><view class="textarea"><block wx:if="{{r0}}"><textarea maxlength="200" placeholder="备注最多200字" value="{{s}}" bindinput="{{t}}"></textarea></block></view><view class="bottom"><button class="primary" bindtap="{{v}}">保存</button></view></view>
<view class="page"><view class="field"><text class="label">客户名称</text><input class="value" placeholder="必填" value="{{a}}" bindinput="{{b}}"/></view><view class="field"><text class="label">售价档位</text><picker range="{{d}}" value="{{e}}" bindchange="{{f}}"><view class="value">{{c}}</view></picker></view><view class="field"><text class="label">联系人</text><input class="value" placeholder="可选" value="{{g}}" bindinput="{{h}}"/></view><view class="field"><text class="label">手机</text><input class="value" placeholder="可选" value="{{i}}" bindinput="{{j}}"/></view><view class="field"><text class="label">电话</text><input class="value" placeholder="可选(座机)" value="{{k}}" bindinput="{{l}}"/></view><view class="field"><text class="label">送货地址</text><input class="value" placeholder="可选" value="{{m}}" bindinput="{{n}}"/></view><view class="field"><text class="label">初始应收</text><input class="value" type="digit" placeholder="默认 0.00" value="{{o}}" bindinput="{{p}}"/></view><view class="textarea"><block wx:if="{{r0}}"><textarea maxlength="200" placeholder="备注最多200字" value="{{q}}" bindinput="{{r}}"></textarea></block></view><view class="bottom"><button class="primary" bindtap="{{s}}">保存</button></view></view>

View File

@@ -5,8 +5,7 @@ const API_OF = {
sale: "/api/orders",
purchase: "/api/purchase-orders",
collect: "/api/payments",
fund: "/api/other-transactions",
stock: "/api/inventories/logs"
fund: "/api/other-transactions"
};
const _sfc_main = {
data() {
@@ -16,18 +15,19 @@ const _sfc_main = {
{ key: "sale", name: "出货" },
{ key: "purchase", name: "进货" },
{ key: "collect", name: "收款" },
{ key: "fund", name: "资金" },
{ key: "stock", name: "盘点" }
{ key: "fund", name: "资金" }
],
range: "month",
query: { kw: "" },
items: [],
page: 1,
size: 20,
size: 15,
finished: false,
loading: false,
startDate: "",
endDate: ""
endDate: "",
nonVipRetentionDays: 0,
isVip: false
};
},
computed: {
@@ -56,7 +56,7 @@ const _sfc_main = {
return;
}
try {
common_vendor.index.__f__("log", "at pages/detail/index.vue:102", "[detail] onLoad route = pages/detail/index");
common_vendor.index.__f__("log", "at pages/detail/index.vue:104", "[detail] onLoad route = pages/detail/index");
} catch (e) {
}
this.computeRange();
@@ -133,12 +133,43 @@ const _sfc_main = {
if (list.length < this.size)
this.finished = true;
this.page += 1;
await this.hintIfNonVipOutOfWindow();
} catch (e) {
common_vendor.index.showToast({ title: "加载失败", icon: "none" });
} finally {
this.loading = false;
}
},
async hintIfNonVipOutOfWindow() {
try {
if (this.isVip && this.isVip === true)
return;
if (!this.nonVipRetentionDays) {
const v = await common_http.get("/api/vip/status");
this.isVip = !!(v == null ? void 0 : v.isVip);
this.nonVipRetentionDays = Number((v == null ? void 0 : v.nonVipRetentionDays) || 60);
if (this.isVip)
return;
}
if (!this.startDate)
return;
const start = new Date(this.startDate).getTime();
const threshold = Date.now() - this.nonVipRetentionDays * 24 * 3600 * 1e3;
if (start < threshold) {
common_vendor.index.showModal({
title: "提示",
content: `普通用户仅显示近${this.nonVipRetentionDays}天数据开通VIP可查看全部历史。`,
confirmText: "去开通VIP",
cancelText: "我知道了",
success: (r) => {
if (r.confirm)
common_vendor.index.navigateTo({ url: "/pages/my/vip" });
}
});
}
} catch (e) {
}
},
formatDate(s) {
if (!s)
return "";
@@ -201,8 +232,14 @@ function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
};
})
} : {}, {
p: common_vendor.o((...args) => $options.loadMore && $options.loadMore(...args)),
q: common_vendor.o((...args) => $options.onCreate && $options.onCreate(...args))
p: $data.items.length && !$data.finished
}, $data.items.length && !$data.finished ? {
q: $data.loading
} : {}, {
r: $data.finished && $data.items.length
}, $data.finished && $data.items.length ? {} : {}, {
s: common_vendor.o((...args) => $options.loadMore && $options.loadMore(...args)),
t: common_vendor.o((...args) => $options.onCreate && $options.onCreate(...args))
});
}
const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render]]);

View File

@@ -1 +1 @@
<view class="page"><view class="content"><view class="biz-tabs"><view wx:for="{{a}}" wx:for-item="b" wx:key="b" class="{{['biz', b.c]}}" bindtap="{{b.d}}">{{b.a}}</view></view><view class="panel"><view class="toolbar"><view class="period-group"><text class="period-label">期间</text><picker mode="date" value="{{c}}" bindchange="{{d}}"><view class="date-chip">{{b}}</view></picker><text class="sep">~</text><picker mode="date" value="{{f}}" bindchange="{{g}}"><view class="date-chip">{{e}}</view></picker></view><view class="search-row"><view class="search"><input class="search-input" placeholder="{{h}}" bindconfirm="{{i}}" value="{{j}}" bindinput="{{k}}"/></view><button class="btn" size="mini" bindtap="{{l}}">查询</button></view></view><view class="total">合计:¥{{m}}</view><scroll-view scroll-y class="list" bindscrolltolower="{{p}}"><block wx:if="{{n}}"><view wx:for="{{o}}" wx:for-item="it" wx:key="g" class="item" bindtap="{{it.h}}"><view class="item-left"><view class="date">{{it.a}}</view><view class="name">{{it.b}}</view><view class="no">{{it.c}}</view></view><view class="{{['amount', it.e && 'in', it.f && 'out']}}">¥ {{it.d}}</view><view class="arrow"></view></view></block><view wx:else class="empty">暂无数据</view></scroll-view><view class="fab" bindtap="{{q}}"></view></view></view></view>
<view class="page"><view class="content"><view class="biz-tabs"><view wx:for="{{a}}" wx:for-item="b" wx:key="b" class="{{['biz', b.c]}}" bindtap="{{b.d}}">{{b.a}}</view></view><view class="panel"><view class="toolbar"><view class="period-group"><text class="period-label">期间</text><picker mode="date" value="{{c}}" bindchange="{{d}}"><view class="date-chip">{{b}}</view></picker><text class="sep">~</text><picker mode="date" value="{{f}}" bindchange="{{g}}"><view class="date-chip">{{e}}</view></picker></view><view class="search-row"><view class="search"><input class="search-input" placeholder="{{h}}" bindconfirm="{{i}}" value="{{j}}" bindinput="{{k}}"/></view><button class="btn" size="mini" bindtap="{{l}}">查询</button></view></view><view class="total">合计:¥{{m}}</view><scroll-view scroll-y class="list" bindscrolltolower="{{s}}"><block wx:if="{{n}}"><view wx:for="{{o}}" wx:for-item="it" wx:key="g" class="item" bindtap="{{it.h}}"><view class="item-left"><view class="date">{{it.a}}</view><view class="name">{{it.b}}</view><view class="no">{{it.c}}</view></view><view class="{{['amount', it.e && 'in', it.f && 'out']}}">¥ {{it.d}}</view><view class="arrow"></view></view></block><view wx:else class="empty">暂无数据</view><view wx:if="{{p}}" class="loading" hidden="{{!q}}">加载中...</view><view wx:if="{{r}}" class="finished">没有更多了</view></scroll-view><view class="fab" bindtap="{{t}}"></view></view></view></view>

View File

@@ -66,7 +66,6 @@
margin: 16rpx;
border-radius: 16rpx;
padding: 12rpx;
border: 2rpx solid #e5e7eb;
}
.toolbar {
display: flex;
@@ -80,7 +79,6 @@
align-items: center;
gap: 8rpx;
background: #f6f8fb;
border: 2rpx solid #e6ebf2;
border-radius: 10rpx;
padding: 8rpx 10rpx;
}
@@ -100,23 +98,42 @@
.search-row {
display: flex;
align-items: center;
gap: 10rpx;
gap: 16rpx;
}
.search {
flex: 1;
min-width: 0;
display: flex;
}
.search-input {
width: 100%;
flex: 1;
height: 72rpx;
line-height: 72rpx;
padding: 0 24rpx;
box-sizing: border-box;
background: #fff;
border-radius: 12rpx;
padding: 12rpx;
color: #111;
border: 2rpx solid #e6ebf2;
font-size: 26rpx;
}
.btn {
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
height: 72rpx;
padding: 0 32rpx;
margin-left: 4rpx;
border-radius: 12rpx;
background: #4C8DFF;
color: #fff;
border: none;
font-size: 26rpx;
box-sizing: border-box;
}
.btn::after {
border: none;
}
.total {
color: #4C8DFF;
@@ -127,6 +144,16 @@
.list {
flex: 1;
}
.loading {
text-align: center;
padding: 20rpx 0;
color: #444;
}
.finished {
text-align: center;
padding: 20rpx 0;
color: #444;
}
.item {
display: grid;
grid-template-columns: 1fr auto auto;

View File

@@ -12,17 +12,19 @@ const _sfc_main = {
notices: [],
loadingNotices: false,
noticeError: "",
consultLabel: "咨询",
consultDialogVisible: false,
consultMessage: "",
features: [
{ key: "product", title: "货品", img: "/static/icons/product.png", emoji: "📦" },
{ key: "customer", title: "客户", img: "/static/icons/customer.png", emoji: "👥" },
{ key: "sale", title: "销售", img: "/static/icons/sale.png", emoji: "💰" },
{ key: "account", title: "账户", img: "/static/icons/account.png", emoji: "💳" },
{ key: "supplier", title: "供应商", img: "/static/icons/supplier.png", emoji: "🚚" },
{ key: "purchase", title: "进货", img: "/static/icons/purchase.png", emoji: "🛒" },
{ key: "otherPay", title: "其他支出", img: "/static/icons/other-pay.png", emoji: "💸" },
{ key: "vip", title: "VIP会员", img: "/static/icons/vip.png", emoji: "👑" },
{ key: "report", title: "报表", img: "/static/icons/report.png", emoji: "📊" },
{ key: "more", title: "更多", img: "/static/icons/more.png", emoji: "⋯" }
{ key: "customer", title: "客户", img: "/static/icons/webwxgetmsgimg.png", emoji: "👥" },
{ key: "sale", title: "销售", img: "/static/icons/webwxgetmsgimg.jpg", emoji: "💰" },
{ key: "account", title: "账户", img: "/static/icons/icons8-profile-50.png", emoji: "💳" },
{ key: "supplier", title: "供应商", img: "/static/icons/icons8-supplier-50.png", emoji: "🚚" },
{ key: "purchase", title: "进货", img: "/static/icons/icons8-dollar-ethereum-exchange-50.png", emoji: "🛒" },
{ key: "otherPay", title: "其他支出", img: "/static/icons/icons8-expenditure-64.png", emoji: "💸" },
{ key: "vip", title: "VIP会员", img: "/static/icons/icons8-vip-48.png", emoji: "👑" },
{ key: "report", title: "报表", img: "/static/icons/icons8-graph-report-50.png", emoji: "📊" }
]
};
},
@@ -42,6 +44,7 @@ const _sfc_main = {
}
this.fetchMetrics();
this.fetchNotices();
this.fetchLatestConsult();
},
methods: {
async fetchMetrics() {
@@ -58,6 +61,59 @@ const _sfc_main = {
} catch (e) {
}
},
async fetchLatestConsult() {
try {
const d = await common_http.get("/api/consults");
if (d && d.replied)
this.consultLabel = "已回复";
else
this.consultLabel = "咨询";
this._latestConsult = d;
} catch (e) {
this.consultLabel = "咨询";
}
},
onConsultTap() {
if (this.consultLabel === "已回复" && this._latestConsult && this._latestConsult.id) {
const msg = this._latestConsult.latestReply ? this._latestConsult.latestReply : this._latestConsult.message || "";
common_vendor.index.showModal({ title: "咨询回复", content: msg || "暂无内容", showCancel: false, success: async (res) => {
if (!res || res.confirm !== true)
return;
try {
const r = await common_http.put(`/api/consults/${this._latestConsult.id}/ack`, {});
this.consultLabel = "咨询";
this._latestConsult = null;
setTimeout(() => this.fetchLatestConsult(), 200);
} catch (e) {
try {
common_vendor.index.showToast({ title: e && e.message || "已读同步失败", icon: "none" });
} catch (_) {
}
}
} });
return;
}
this.consultMessage = "";
this.consultDialogVisible = true;
},
closeConsultDialog() {
this.consultDialogVisible = false;
},
async submitConsult() {
const text = String(this.consultMessage || "").trim();
if (!text) {
common_vendor.index.showToast({ title: "请输入咨询内容", icon: "none" });
return;
}
try {
await common_http.post("/api/consults", { message: text });
this.consultDialogVisible = false;
common_vendor.index.showToast({ title: "已提交", icon: "success" });
setTimeout(() => this.fetchLatestConsult(), 300);
} catch (e) {
common_vendor.index.showToast({ title: e && e.message || "提交失败", icon: "none" });
}
},
async fetchNotices() {
this.loadingNotices = true;
this.noticeError = "";
@@ -128,7 +184,7 @@ const _sfc_main = {
},
goDetail() {
try {
common_vendor.index.__f__("log", "at pages/index/index.vue:198", "[index] goDetail → /pages/detail/index");
common_vendor.index.__f__("log", "at pages/index/index.vue:253", "[index] goDetail → /pages/detail/index");
} catch (e) {
}
common_vendor.index.switchTab({ url: "/pages/detail/index" });
@@ -150,11 +206,32 @@ const _sfc_main = {
};
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
return common_vendor.e({
a: $data.loadingNotices
a: common_vendor.t($data.consultLabel),
b: common_vendor.o((...args) => $options.onConsultTap && $options.onConsultTap(...args)),
c: $data.KPI_ICONS.todaySales,
d: common_vendor.t($data.kpi.todaySales),
e: $data.KPI_ICONS.monthSales,
f: common_vendor.t($data.kpi.monthSales),
g: $data.KPI_ICONS.monthProfit,
h: common_vendor.t($data.kpi.monthProfit),
i: $data.KPI_ICONS.stockCount,
j: common_vendor.t($data.kpi.stockCount),
k: $data.consultDialogVisible
}, $data.consultDialogVisible ? {
l: $data.consultMessage,
m: common_vendor.o(($event) => $data.consultMessage = $event.detail.value),
n: common_vendor.o((...args) => $options.closeConsultDialog && $options.closeConsultDialog(...args)),
o: common_vendor.o((...args) => $options.submitConsult && $options.submitConsult(...args)),
p: common_vendor.o(() => {
}),
q: common_vendor.o(() => {
})
} : {}, {
r: $data.loadingNotices
}, $data.loadingNotices ? {} : $data.noticeError ? {
c: common_vendor.t($data.noticeError)
t: common_vendor.t($data.noticeError)
} : !$data.notices.length ? {} : {
e: common_vendor.f($data.notices, (n, idx, i0) => {
w: common_vendor.f($data.notices, (n, idx, i0) => {
return common_vendor.e({
a: common_vendor.t(n.text),
b: n.tag
@@ -166,17 +243,9 @@ function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
});
})
}, {
b: $data.noticeError,
d: !$data.notices.length,
f: $data.KPI_ICONS.todaySales,
g: common_vendor.t($data.kpi.todaySales),
h: $data.KPI_ICONS.monthSales,
i: common_vendor.t($data.kpi.monthSales),
j: $data.KPI_ICONS.monthProfit,
k: common_vendor.t($data.kpi.monthProfit),
l: $data.KPI_ICONS.stockCount,
m: common_vendor.t($data.kpi.stockCount),
n: common_vendor.f($data.features, (item, k0, i0) => {
s: $data.noticeError,
v: !$data.notices.length,
x: common_vendor.f($data.features, (item, k0, i0) => {
return common_vendor.e({
a: item.img
}, item.img ? {

View File

@@ -1 +1 @@
<view class="home"><view class="notice"><view class="notice-left">公告</view><view wx:if="{{a}}" class="notice-swiper" style="display:flex;align-items:center;color:#6b5a2a">加载中...</view><view wx:elif="{{b}}" class="notice-swiper" style="display:flex;align-items:center;color:#dd524d">{{c}}</view><view wx:elif="{{d}}" class="notice-swiper" style="display:flex;align-items:center;color:#6b5a2a">暂无公告</view><swiper wx:else class="notice-swiper" circular autoplay interval="4000" duration="400" vertical><swiper-item wx:for="{{e}}" wx:for-item="n" wx:key="e"><view class="notice-item" bindtap="{{n.d}}"><text class="notice-text">{{n.a}}</text><text wx:if="{{n.b}}" class="notice-tag">{{n.c}}</text></view></swiper-item></swiper></view><view class="hero"><view class="hero-top"><text class="brand">五金配件管家</text><view class="cta"><text class="cta-text">咨询</text></view></view><view class="kpi kpi-grid"><view class="kpi-item kpi-card"><image src="{{f}}" class="kpi-icon" mode="aspectFit"></image><view class="kpi-content"><text class="kpi-label">今日销售额</text><text class="kpi-value">{{g}}</text></view></view><view class="kpi-item kpi-card"><image src="{{h}}" class="kpi-icon" mode="aspectFit"></image><view class="kpi-content"><text class="kpi-label">本月销售额</text><text class="kpi-value">{{i}}</text></view></view><view class="kpi-item kpi-card"><image src="{{j}}" class="kpi-icon" mode="aspectFit"></image><view class="kpi-content"><text class="kpi-label">本月利润</text><text class="kpi-value">{{k}}</text></view></view><view class="kpi-item kpi-card"><image src="{{l}}" class="kpi-icon" mode="aspectFit"></image><view class="kpi-content"><text class="kpi-label">库存商品数量</text><text class="kpi-value">{{m}}</text></view></view></view></view><view class="section-title"><text class="section-text">常用功能</text></view><view class="grid-wrap"><view class="feature-grid"><view wx:for="{{n}}" wx:for-item="item" wx:key="g" class="feature-card" bindtap="{{item.h}}"><view class="fc-icon"><image wx:if="{{item.a}}" src="{{item.b}}" class="fc-img" mode="aspectFit" binderror="{{item.c}}"></image><text wx:elif="{{item.d}}" class="fc-emoji">{{item.e}}</text><view wx:else class="fc-placeholder"></view></view><view class="fc-title">{{item.f}}</view></view></view></view></view>
<view class="home"><view class="hero"><view class="hero-top"><text class="brand">五金配件管家</text><view class="cta" bindtap="{{b}}" hover-class="cta-active" hover-stay-time="80"><text class="cta-text">{{a}}</text></view></view><view class="kpi kpi-grid"><view class="kpi-item kpi-card"><image src="{{c}}" class="kpi-icon" mode="aspectFit"></image><view class="kpi-content"><text class="kpi-label">今日销售额</text><text class="kpi-value">{{d}}</text></view></view><view class="kpi-item kpi-card"><image src="{{e}}" class="kpi-icon" mode="aspectFit"></image><view class="kpi-content"><text class="kpi-label">本月销售额</text><text class="kpi-value">{{f}}</text></view></view><view class="kpi-item kpi-card"><image src="{{g}}" class="kpi-icon" mode="aspectFit"></image><view class="kpi-content"><text class="kpi-label">本月利润</text><text class="kpi-value">{{h}}</text></view></view><view class="kpi-item kpi-card"><image src="{{i}}" class="kpi-icon" mode="aspectFit"></image><view class="kpi-content"><text class="kpi-label">库存商品数量</text><text class="kpi-value">{{j}}</text></view></view></view></view><view wx:if="{{k}}" class="dialog-mask" catchtouchmove="{{p}}" catchtap="{{q}}"><view class="dialog"><view class="dialog-title">咨询</view><block wx:if="{{r0}}"><textarea class="dialog-textarea" placeholder="请输入咨询内容..." maxlength="500" value="{{l}}" bindinput="{{m}}"></textarea></block><view class="dialog-actions"><view class="btn" bindtap="{{n}}">取消</view><view class="btn primary" bindtap="{{o}}">提交</view></view></view></view><view class="notice"><view class="notice-left">公告</view><view wx:if="{{r}}" class="notice-swiper" style="display:flex;align-items:center;color:#6b5a2a">加载中...</view><view wx:elif="{{s}}" class="notice-swiper" style="display:flex;align-items:center;color:#dd524d">{{t}}</view><view wx:elif="{{v}}" class="notice-swiper" style="display:flex;align-items:center;color:#6b5a2a">暂无公告</view><swiper wx:else class="notice-swiper" circular autoplay interval="4000" duration="400" vertical><swiper-item wx:for="{{w}}" wx:for-item="n" wx:key="e"><view class="notice-item" bindtap="{{n.d}}"><text class="notice-text">{{n.a}}</text><text wx:if="{{n.b}}" class="notice-tag">{{n.c}}</text></view></swiper-item></swiper></view><view class="section-title"><text class="section-text">常用功能</text></view><view class="grid-wrap"><view class="feature-grid"><view wx:for="{{x}}" wx:for-item="item" wx:key="g" class="feature-card" bindtap="{{item.h}}"><view class="fc-icon"><image wx:if="{{item.a}}" src="{{item.b}}" class="fc-img" mode="aspectFit" binderror="{{item.c}}"></image><text wx:elif="{{item.d}}" class="fc-emoji">{{item.e}}</text><view wx:else class="fc-placeholder"></view></view><view class="fc-title">{{item.f}}</view></view></view></view></view>

View File

@@ -24,12 +24,21 @@
/* 垂直间距 */
/* 透明度 */
/* 文章场景相关 */
page {
height: 100%;
overflow: hidden;
background: linear-gradient(180deg, #f8fbff 0%, #ffffff 60%);
}
.home {
padding-bottom: 140rpx;
height: 100vh;
display: flex;
flex-direction: column;
padding-bottom: calc(env(safe-area-inset-bottom) + 32rpx);
position: relative;
/* 渐变背景:顶部淡蓝过渡到白色 */
background: linear-gradient(180deg, #f8fbff 0%, #ffffff 60%);
min-height: 100vh;
overflow: hidden;
box-sizing: border-box;
}
/* 首页横幅(移除) */
@@ -92,6 +101,7 @@
align-items: center;
gap: 16rpx;
padding: 10rpx 28rpx 0;
flex: 0 0 auto;
}
.section-title::before {
content: "";
@@ -111,12 +121,13 @@
/* 顶部英雄区:浅色玻璃卡片,带金色描边与柔和阴影 */
.hero {
margin: 16rpx 20rpx;
padding: 18rpx;
padding: 18rpx 18rpx 12rpx;
border-radius: 20rpx;
background: #ffffff;
border: 2rpx solid #e5e7eb;
box-shadow: none;
color: #111;
flex: 0 0 auto;
}
.hero-top {
display: flex;
@@ -159,11 +170,65 @@
letter-spacing: 1rpx;
}
/* KPI 卡片化布局2×2 */
/* 简易弹层样式 */
.dialog-mask {
position: fixed;
left: 0;
right: 0;
top: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.45);
display: flex;
align-items: center;
justify-content: center;
z-index: 10000;
}
.dialog {
width: 82vw;
background: #fff;
border-radius: 16rpx;
padding: 20rpx;
border: 2rpx solid #eef2f6;
}
.dialog-title {
font-size: 32rpx;
font-weight: 800;
color: #111;
margin-bottom: 16rpx;
}
.dialog-textarea {
width: 100%;
min-height: 180rpx;
border: 2rpx solid #e8eef8;
border-radius: 12rpx;
padding: 12rpx;
box-sizing: border-box;
}
.dialog-actions {
display: flex;
justify-content: flex-end;
gap: 18rpx;
margin-top: 16rpx;
}
.btn {
padding: 10rpx 22rpx;
border-radius: 999rpx;
background: #f3f6fb;
color: #334155;
border: 2rpx solid #e2e8f0;
font-weight: 700;
}
.btn.primary {
background: #4C8DFF;
color: #fff;
border-color: #4C8DFF;
}
/* KPI 卡片化布局:横向铺满 */
.kpi {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 16rpx;
grid-template-columns: repeat(4, 1fr);
gap: 12rpx;
}
.kpi-item {
text-align: center;
@@ -175,19 +240,21 @@
/* KPI 卡片(更扁平,降低高度) */
.kpi-grid {
grid-template-columns: repeat(2, 1fr);
gap: 16rpx;
gap: 12rpx;
}
.kpi-card {
display: flex;
align-items: center;
gap: 12rpx;
flex-direction: column;
align-items: flex-start;
justify-content: center;
gap: 8rpx;
text-align: left;
padding: 12rpx 14rpx;
border-radius: 12rpx;
border-radius: 14rpx;
background: #fff;
border: 2rpx solid #eef2f6;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.04);
min-height: 120rpx;
}
.kpi-icon {
width: 44rpx;
@@ -206,8 +273,8 @@
}
.kpi-value {
color: #4C8DFF;
font-size: 36rpx;
line-height: 40rpx;
font-size: 34rpx;
line-height: 38rpx;
margin-top: 0;
font-weight: 800;
}
@@ -241,45 +308,56 @@
/* 功能容器:更轻的留白 */
.grid-wrap {
margin: 8rpx 12rpx 24rpx;
padding: 8rpx 8rpx 0;
border-radius: 20rpx;
background: transparent;
border: 0;
flex: 1 1 auto;
display: flex;
align-items: stretch;
justify-content: center;
margin: 16rpx 20rpx 28rpx;
padding: 32rpx 30rpx;
border-radius: 26rpx;
background: rgba(255, 255, 255, 0.96);
border: 2rpx solid #edf2f9;
box-shadow: 0 12rpx 28rpx rgba(32, 75, 143, 0.1);
box-sizing: border-box;
}
/* 功能卡片宫格:方形竖排,图标在上文字在下(与截图一致) */
.feature-grid {
flex: 1 1 auto;
width: 100%;
height: 100%;
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 14rpx;
padding: 8rpx 8rpx 18rpx;
grid-template-columns: repeat(3, minmax(0, 1fr));
grid-auto-rows: 1fr;
gap: 32rpx 28rpx;
align-content: space-evenly;
justify-items: center;
}
.feature-card {
height: 164rpx;
width: 168rpx;
height: 176rpx;
background: #fff;
border: 2rpx solid #eef2f6;
border-radius: 16rpx;
box-shadow: 0 6rpx 16rpx rgba(0, 0, 0, 0.04);
padding: 12rpx;
border-radius: 20rpx;
box-shadow: 0 10rpx 24rpx rgba(0, 0, 0, 0.05);
padding: 18rpx 16rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.fc-icon {
width: 86rpx;
height: 86rpx;
width: 78rpx;
height: 78rpx;
border-radius: 18rpx;
background: #f7faff;
border: 2rpx solid #e8eef8;
display: flex;
align-items: center;
justify-content: center;
}
.fc-img {
width: 56rpx;
height: 56rpx;
width: 54rpx;
height: 54rpx;
opacity: 0.95;
}
.fc-emoji {
@@ -293,10 +371,11 @@
border: 2rpx solid #e8eef8;
}
.fc-title {
margin-top: 10rpx;
font-size: 26rpx;
margin-top: 12rpx;
font-size: 28rpx;
font-weight: 700;
color: #111;
letter-spacing: 1rpx;
}
/* 底部操作条:浅色半透明 + 金色主按钮 */

View File

@@ -16,7 +16,7 @@ const _sfc_main = {
};
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
return {
a: common_assets._imports_0$1,
a: common_assets._imports_0$2,
b: common_vendor.o((...args) => $options.openPolicy && $options.openPolicy(...args)),
c: common_vendor.o((...args) => $options.openTerms && $options.openTerms(...args)),
d: common_vendor.o((...args) => $options.openComplaint && $options.openComplaint(...args))

View File

@@ -1,10 +1,24 @@
"use strict";
const common_vendor = require("../../common/vendor.js");
const common_http = require("../../common/http.js");
const common_config = require("../../common/config.js");
const common_assets = require("../../common/assets.js");
function normalizeAvatar(url) {
if (!url)
return "/static/icons/icons8-mitt-24.png";
const s = String(url);
if (/^https?:\/\//i.test(s))
return s;
if (!common_config.API_BASE_URL)
return s;
if (s.startsWith("/"))
return `${common_config.API_BASE_URL}${s}`;
return `${common_config.API_BASE_URL}/${s}`;
}
const _sfc_main = {
data() {
return {
avatarUrl: "/static/logo.png",
avatarUrl: "/static/icons/icons8-mitt-24.png",
shopName: "未登录",
mobile: "",
pendingJsCode: "",
@@ -16,7 +30,7 @@ const _sfc_main = {
},
onShow() {
this.fetchProfile();
this.loadVipFromStorage();
this.loadVip();
try {
if (common_vendor.index.getStorageSync("TOKEN")) {
this.$forceUpdate && this.$forceUpdate();
@@ -32,9 +46,22 @@ const _sfc_main = {
return false;
}
},
mobileDisplay() {
const m = String(this.mobile || "");
return m.length === 11 ? m.slice(0, 3) + "****" + m.slice(7) : m || "未绑定手机号";
avatarDisplay() {
return normalizeAvatar(this.avatarUrl);
},
emailDisplay() {
if (!this.isLoggedIn)
return "";
const e = String(common_vendor.index.getStorageSync("USER_EMAIL") || "");
if (!e)
return "未绑定邮箱";
const at = e.indexOf("@");
if (at > 1) {
const name = e.slice(0, at);
const domain = e.slice(at);
return (name.length <= 2 ? name[0] + "*" : name.slice(0, 2) + "***") + domain;
}
return e;
},
vipStartDisplay() {
return this.formatDisplay(this.vipStart);
@@ -55,35 +82,100 @@ const _sfc_main = {
})();
if (!hasToken) {
this.shopName = "未登录";
this.avatarUrl = "/static/logo.png";
this.avatarUrl = "/static/icons/icons8-mitt-24.png";
this.mobile = "";
return;
}
try {
await common_http.get("/api/dashboard/overview");
} catch (e) {
}
try {
const storeName = common_vendor.index.getStorageSync("SHOP_NAME") || "";
const avatar = common_vendor.index.getStorageSync("USER_AVATAR") || "";
const phone = common_vendor.index.getStorageSync("USER_MOBILE") || "";
if (storeName)
this.shopName = storeName;
if (avatar)
this.avatarUrl = avatar;
const profile = await common_http.get("/api/user/me");
const latestAvatar = (profile == null ? void 0 : profile.avatarUrl) || "";
if (latestAvatar) {
const bust = `${latestAvatar}${latestAvatar.includes("?") ? "&" : "?"}t=${Date.now()}`;
this.avatarUrl = bust;
try {
common_vendor.index.setStorageSync("USER_AVATAR_RAW", latestAvatar);
common_vendor.index.setStorageSync("USER_AVATAR", latestAvatar);
} catch (_) {
}
} else {
const cached = common_vendor.index.getStorageSync("USER_AVATAR") || "";
this.avatarUrl = cached || "/static/icons/icons8-mitt-24.png";
}
const storeName = (profile == null ? void 0 : profile.name) || common_vendor.index.getStorageSync("SHOP_NAME") || "未命名店铺";
this.shopName = storeName;
const phone = (profile == null ? void 0 : profile.phone) || common_vendor.index.getStorageSync("USER_MOBILE") || "";
this.mobile = phone;
} catch (e) {
try {
const storeName = common_vendor.index.getStorageSync("SHOP_NAME") || "";
const avatar = common_vendor.index.getStorageSync("USER_AVATAR") || "";
const phone = common_vendor.index.getStorageSync("USER_MOBILE") || "";
if (storeName)
this.shopName = storeName;
if (avatar)
this.avatarUrl = avatar;
this.mobile = phone;
} catch (_) {
}
}
},
loadVipFromStorage() {
async loadVip() {
try {
const isVip = String(common_vendor.index.getStorageSync("USER_VIP_IS_VIP") || "false").toLowerCase() === "true";
const start = common_vendor.index.getStorageSync("USER_VIP_START") || "";
const end = common_vendor.index.getStorageSync("USER_VIP_END") || "";
this.vipIsVip = isVip;
this.vipStart = start;
this.vipEnd = end;
const hasToken = (() => {
try {
return !!common_vendor.index.getStorageSync("TOKEN");
} catch (e) {
return false;
}
})();
if (!hasToken) {
this.vipIsVip = false;
this.vipStart = "";
this.vipEnd = "";
return;
}
const data = await common_http.get("/api/vip/status");
const active = !!(data == null ? void 0 : data.isVip);
this.vipIsVip = active;
this.vipEnd = (data == null ? void 0 : data.expireAt) || "";
let computedStart = "";
const exp = this.vipEnd;
if (exp) {
const m = String(exp).match(/^(\d{4})-(\d{2})-(\d{2})(?:[ T](\d{2}):(\d{2})(?::(\d{2}))?)?/);
if (m) {
const y = Number(m[1]);
const mo = Number(m[2]) - 1;
const da = Number(m[3]);
const hh = Number(m[4] || "0");
const mm = Number(m[5] || "0");
const ss = Number(m[6] || "0");
const startDate = new Date(y, mo - 1, da, hh, mm, ss);
const y2 = startDate.getFullYear();
const m2 = (startDate.getMonth() + 1).toString().padStart(2, "0");
const d2 = startDate.getDate().toString().padStart(2, "0");
const h2 = startDate.getHours().toString().padStart(2, "0");
const i2 = startDate.getMinutes().toString().padStart(2, "0");
computedStart = `${y2}-${m2}-${d2} ${h2}:${i2}`;
}
}
this.vipStart = computedStart;
try {
common_vendor.index.setStorageSync("USER_VIP_IS_VIP", String(active));
common_vendor.index.setStorageSync("USER_VIP_END", this.vipEnd);
if (this.vipStart)
common_vendor.index.setStorageSync("USER_VIP_START", this.vipStart);
else
common_vendor.index.removeStorageSync("USER_VIP_START");
} catch (_) {
}
} catch (e) {
try {
const isVip = String(common_vendor.index.getStorageSync("USER_VIP_IS_VIP") || "false").toLowerCase() === "true";
this.vipIsVip = isVip;
this.vipStart = common_vendor.index.getStorageSync("USER_VIP_START") || "";
this.vipEnd = common_vendor.index.getStorageSync("USER_VIP_END") || "";
} catch (_) {
}
}
},
formatDisplay(value) {
@@ -136,9 +228,6 @@ const _sfc_main = {
goLogin() {
common_vendor.index.navigateTo({ url: "/pages/auth/login" });
},
goRegister() {
common_vendor.index.navigateTo({ url: "/pages/auth/register" });
},
onGetPhoneNumber(e) {
if (this.logging)
return;
@@ -161,34 +250,16 @@ const _sfc_main = {
common_vendor.index.navigateTo({ url: "/pages/my/sms-login" });
},
onAvatarError() {
this.avatarUrl = "/static/logo.png";
this.avatarUrl = "/static/icons/icons8-mitt-24.png";
},
goVip() {
common_vendor.index.navigateTo({ url: "/pages/my/vip" });
},
goMyOrders() {
common_vendor.index.switchTab({ url: "/pages/detail/index" });
},
goSupplier() {
common_vendor.index.navigateTo({ url: "/pages/supplier/select" });
},
goCustomer() {
common_vendor.index.navigateTo({ url: "/pages/customer/select" });
},
goCustomerQuote() {
common_vendor.index.showToast({ title: "客户报价(开发中)", icon: "none" });
},
goShop() {
common_vendor.index.showToast({ title: "店铺管理(开发中)", icon: "none" });
common_vendor.index.navigateTo({ url: "/pages/my/orders" });
},
editProfile() {
common_vendor.index.showToast({ title: "账号与安全(开发中)", icon: "none" });
},
goProductSettings() {
common_vendor.index.navigateTo({ url: "/pages/product/settings" });
},
goSystemParams() {
common_vendor.index.showToast({ title: "系统参数(开发中)", icon: "none" });
common_vendor.index.navigateTo({ url: "/pages/my/security" });
},
goAbout() {
common_vendor.index.navigateTo({ url: "/pages/my/about" });
@@ -201,9 +272,14 @@ const _sfc_main = {
common_vendor.index.removeStorageSync("DEFAULT_USER_ID");
common_vendor.index.setStorageSync("ENABLE_DEFAULT_USER", "false");
common_vendor.index.removeStorageSync("USER_AVATAR");
common_vendor.index.removeStorageSync("USER_AVATAR_RAW");
common_vendor.index.removeStorageSync("USER_NAME");
common_vendor.index.removeStorageSync("USER_MOBILE");
common_vendor.index.removeStorageSync("USER_EMAIL");
common_vendor.index.removeStorageSync("SHOP_NAME");
common_vendor.index.removeStorageSync("USER_VIP_IS_VIP");
common_vendor.index.removeStorageSync("USER_VIP_START");
common_vendor.index.removeStorageSync("USER_VIP_END");
common_vendor.index.showToast({ title: "已清理本地信息", icon: "none" });
setTimeout(() => {
common_vendor.index.reLaunch({ url: "/pages/index/index" });
@@ -216,32 +292,32 @@ const _sfc_main = {
};
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
return common_vendor.e({
a: !$options.isLoggedIn
}, !$options.isLoggedIn ? {
b: common_vendor.o((...args) => $options.goLogin && $options.goLogin(...args)),
c: common_vendor.o((...args) => $options.goRegister && $options.goRegister(...args))
} : {}, {
d: $data.avatarUrl,
e: common_vendor.o((...args) => $options.onAvatarError && $options.onAvatarError(...args)),
f: common_vendor.t($data.shopName),
g: common_vendor.t($options.mobileDisplay),
h: common_vendor.t($data.vipIsVip ? "VIP" : "非VIP"),
i: common_vendor.t($options.vipStartDisplay),
j: common_vendor.t($options.vipEndDisplay),
k: $data.vipIsVip ? 1 : "",
l: common_vendor.o((...args) => $options.goVip && $options.goVip(...args)),
m: common_vendor.o((...args) => $options.goMyOrders && $options.goMyOrders(...args)),
n: common_vendor.o((...args) => $options.goSupplier && $options.goSupplier(...args)),
o: common_vendor.o((...args) => $options.goCustomer && $options.goCustomer(...args)),
p: common_vendor.o((...args) => $options.goCustomerQuote && $options.goCustomerQuote(...args)),
q: common_vendor.o((...args) => $options.goShop && $options.goShop(...args)),
r: common_vendor.o((...args) => $options.editProfile && $options.editProfile(...args)),
s: common_vendor.o((...args) => $options.goProductSettings && $options.goProductSettings(...args)),
t: common_vendor.o((...args) => $options.goSystemParams && $options.goSystemParams(...args)),
v: common_vendor.o((...args) => $options.goAbout && $options.goAbout(...args)),
w: $options.isLoggedIn
a: $options.isLoggedIn
}, $options.isLoggedIn ? {
x: common_vendor.o((...args) => $options.logout && $options.logout(...args))
b: $options.avatarDisplay,
c: common_vendor.o((...args) => $options.onAvatarError && $options.onAvatarError(...args)),
d: common_vendor.t($data.shopName),
e: common_vendor.t($options.emailDisplay)
} : {
f: common_assets._imports_0$1,
g: common_vendor.o((...args) => $options.goLogin && $options.goLogin(...args))
}, {
h: $options.isLoggedIn
}, $options.isLoggedIn ? {
i: common_vendor.t($data.vipIsVip ? "VIP" : "非VIP"),
j: common_vendor.t($options.vipStartDisplay),
k: common_vendor.t($options.vipEndDisplay),
l: $data.vipIsVip ? 1 : ""
} : {}, {
m: $data.vipIsVip
}, $data.vipIsVip ? {} : {}, {
n: common_vendor.o((...args) => $options.goVip && $options.goVip(...args)),
o: common_vendor.o((...args) => $options.goMyOrders && $options.goMyOrders(...args)),
p: common_vendor.o((...args) => $options.editProfile && $options.editProfile(...args)),
q: common_vendor.o((...args) => $options.goAbout && $options.goAbout(...args)),
r: $options.isLoggedIn
}, $options.isLoggedIn ? {
s: common_vendor.o((...args) => $options.logout && $options.logout(...args))
} : {});
}
const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render]]);

View File

@@ -1 +1 @@
<view class="me"><view wx:if="{{a}}" class="card login"><view class="login-title">登录/注册以同步数据</view><button class="login-btn" type="primary" bindtap="{{b}}">登录</button><button class="login-btn minor" bindtap="{{c}}">注册</button></view><view class="card user"><image class="avatar" src="{{d}}" mode="aspectFill" binderror="{{e}}"/><view class="meta"><text class="name">{{f}}</text><text class="phone">{{g}}</text><text class="role">老板</text></view></view><view class="{{['card', 'vip', k && 'active']}}"><view class="vip-row"><text class="vip-badge">{{h}}</text><text class="vip-title">会员状态</text></view><view class="vip-meta"><view class="item"><text class="label">开始</text><text class="value">{{i}}</text></view><view class="item"><text class="label">结束</text><text class="value">{{j}}</text></view></view></view><view class="group"><view class="group-title">会员与订单</view><view class="cell" bindtap="{{l}}"><text>VIP会员</text><text class="arrow"></text></view><view class="cell" bindtap="{{m}}"><text>我的订单</text><text class="arrow"></text></view></view><view class="group"><view class="group-title">基础管理</view><view class="cell" bindtap="{{n}}"><text>供应商管理</text><text class="arrow"></text></view><view class="cell" bindtap="{{o}}"><text>客户管理</text><text class="arrow"></text></view><view class="cell" bindtap="{{p}}"><text>客户报价</text><text class="arrow"></text></view><view class="cell" bindtap="{{q}}"><text>店铺管理</text><text class="arrow"></text></view></view><view class="group"><view class="group-title">设置中心</view><view class="cell" bindtap="{{r}}"><text>账号与安全</text><text class="desc">修改头像、姓名、密码</text><text class="arrow"></text></view><view class="cell" bindtap="{{s}}"><text>商品设置</text><text class="arrow"></text></view><view class="cell" bindtap="{{t}}"><text>系统参数</text><text class="desc">低价提示、默认收款、单行折扣等</text><text class="arrow"></text></view><view class="cell" bindtap="{{v}}"><text>关于与协议</text><text class="arrow"></text></view><view wx:if="{{w}}" class="cell danger" bindtap="{{x}}"><text>退出登录</text></view></view></view>
<view class="me"><view wx:if="{{a}}" class="card user"><image class="avatar" src="{{b}}" mode="aspectFill" binderror="{{c}}"/><view class="meta"><text class="name">{{d}}</text><text class="phone">{{e}}</text><text class="role">老板</text></view></view><view wx:else class="card user guest"><image class="avatar" src="{{f}}" mode="aspectFill"/><view class="meta"><text class="name">未登录</text><text class="phone">登录后同步数据</text><text class="role">访客</text></view><button class="login-entry" bindtap="{{g}}">登录</button></view><view wx:if="{{h}}" class="{{['card', 'vip', l && 'active']}}"><view class="vip-row"><text class="vip-badge">{{i}}</text><text class="vip-title">会员状态</text></view><view class="vip-meta"><view class="item"><text class="label">开始</text><text class="value">{{j}}</text></view><view class="item"><text class="label">结束</text><text class="value">{{k}}</text></view></view></view><view class="group"><view class="group-title">会员与订单</view><view class="cell" bindtap="{{n}}"><view class="cell-left"><text>VIP会员</text><text wx:if="{{m}}" class="vip-tag">已开通</text><text wx:else class="vip-tag pending">待开通</text></view><text class="arrow"></text></view><view class="cell" bindtap="{{o}}"><text>我的订单</text><text class="arrow"></text></view></view><view class="group"><view class="group-title">设置中心</view><view class="cell" bindtap="{{p}}"><text>账号与安全</text><text class="desc">修改头像、姓名、密码、电话</text><text class="arrow"></text></view><view class="cell" bindtap="{{q}}"><text>关于与协议</text><text class="arrow"></text></view><view wx:if="{{r}}" class="cell danger" bindtap="{{s}}"><text>退出登录</text></view></view></view>

View File

@@ -57,6 +57,20 @@
box-shadow: 0 6rpx 16rpx rgba(0, 0, 0, 0.16);
align-items: center;
}
.card.user.guest {
justify-content: space-between;
}
.card.user.guest .meta {
flex: 1;
}
.card.user.guest .login-entry {
padding: 12rpx 30rpx;
border-radius: 999rpx;
background: #4C8DFF;
color: #fff;
font-size: 28rpx;
font-weight: 600;
}
.avatar {
width: 120rpx;
height: 120rpx;
@@ -151,6 +165,23 @@
padding: 26rpx 22rpx;
border-top: 1rpx solid #e5e7eb;
color: #111;
gap: 18rpx;
}
.cell-left {
display: flex;
align-items: center;
gap: 14rpx;
}
.vip-tag {
padding: 4rpx 12rpx;
border-radius: 999rpx;
background: rgba(76, 141, 255, 0.15);
color: #4C8DFF;
font-size: 22rpx;
}
.vip-tag.pending {
background: rgba(76, 141, 255, 0.06);
color: #99a2b3;
}
.cell .desc {
margin-left: auto;

View File

@@ -0,0 +1,75 @@
"use strict";
const common_vendor = require("../../common/vendor.js");
const common_http = require("../../common/http.js");
const _sfc_main = {
data() {
return { list: [], page: 1, size: 20, loading: false };
},
onShow() {
this.fetch(true);
},
computed: {
isLoggedIn() {
try {
return !!common_vendor.index.getStorageSync("TOKEN");
} catch (e) {
return false;
}
}
},
methods: {
async fetch(reset = false) {
if (!this.isLoggedIn)
return;
if (this.loading)
return;
this.loading = true;
try {
const p = reset ? 1 : this.page;
const data = await common_http.get("/api/vip/recharges", { page: p, size: this.size });
const arr = Array.isArray(data == null ? void 0 : data.list) ? data.list : [];
this.list = reset ? arr : (this.list || []).concat(arr);
this.page = p + 1;
} finally {
this.loading = false;
}
},
fmt(v) {
if (!v)
return "";
const s = String(v);
const m = s.match(/^(\d{4}-\d{2}-\d{2})([ T](\d{2}:\d{2}))/);
return m ? `${m[1]} ${m[3]}` : s;
},
toMoney(v) {
try {
return Number(v).toFixed(2);
} catch (_) {
return v;
}
}
}
};
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
return common_vendor.e({
a: !$options.isLoggedIn
}, !$options.isLoggedIn ? {} : common_vendor.e({
b: common_vendor.f($data.list, (it, k0, i0) => {
return common_vendor.e({
a: common_vendor.t($options.toMoney(it.price)),
b: common_vendor.t(it.channel || "支付"),
c: common_vendor.t($options.fmt(it.createdAt)),
d: common_vendor.t(it.durationDays),
e: it.expireTo
}, it.expireTo ? {
f: common_vendor.t($options.fmt(it.expireTo))
} : {}, {
g: it.id
});
}),
c: $data.list.length === 0
}, $data.list.length === 0 ? {} : {}));
}
const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render]]);
wx.createPage(MiniProgramPage);
//# sourceMappingURL=../../../.sourcemap/mp-weixin/pages/my/orders.js.map

View File

@@ -0,0 +1,4 @@
{
"navigationBarTitleText": "我的订单",
"usingComponents": {}
}

View File

@@ -0,0 +1 @@
<view class="orders"><view wx:if="{{a}}" class="hint">请先登录后查看VIP支付记录</view><view wx:else><view wx:for="{{b}}" wx:for-item="it" wx:key="g" class="item"><view class="row1"><text class="price">¥ {{it.a}}</text><text class="channel">{{it.b}}</text></view><view class="row2"><text class="date">{{it.c}}</text><text class="duration">{{it.d}} 天</text></view><view wx:if="{{it.e}}" class="row3"><text class="expire">有效期至 {{it.f}}</text></view></view><view wx:if="{{c}}" class="empty">暂无支付记录</view></view></view>

View File

@@ -24,58 +24,49 @@
/* 垂直间距 */
/* 透明度 */
/* 文章场景相关 */
.login {
min-height: 100vh;
padding: 24rpx;
background: #ffffff;
}
.card {
margin-top: 60rpx;
background: #fff;
border: 2rpx solid #e5e7eb;
border-radius: 16rpx;
padding: 24rpx;
}
.title {
font-size: 36rpx;
font-weight: 800;
margin-bottom: 16rpx;
color: #111;
}
.field {
position: relative;
margin-bottom: 16rpx;
}
.label {
display: block;
margin-bottom: 8rpx;
color: #444;
}
.input {
width: 100%;
background: #f1f1f1;
border: 2rpx solid #e5e7eb;
border-radius: 12rpx;
padding: 14rpx;
color: #111;
}
.toggle {
position: absolute;
right: 12rpx;
top: 64rpx;
color: #4C8DFF;
font-size: 26rpx;
.orders {
padding: 16rpx 16rpx calc(env(safe-area-inset-bottom) + 16rpx);
}
.hint {
color: #444;
font-size: 24rpx;
margin: 8rpx 0 16rpx;
padding: 24rpx;
text-align: center;
}
.primary {
width: 100%;
background: #4C8DFF;
color: #fff;
border-radius: 999rpx;
padding: 20rpx 0;
.item {
background: #fff;
border: 1rpx solid #e5e7eb;
border-radius: 16rpx;
padding: 18rpx;
margin: 12rpx 0;
}
.row1 {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 6rpx;
}
.price {
color: #111;
font-weight: 800;
font-size: 34rpx;
}
.channel {
color: #666;
font-size: 24rpx;
}
.row2 {
display: flex;
justify-content: space-between;
color: #666;
font-size: 24rpx;
}
.row3 {
margin-top: 6rpx;
color: #4C8DFF;
font-size: 24rpx;
}
.empty {
text-align: center;
color: #999;
padding: 40rpx 0;
}

View File

@@ -1,77 +0,0 @@
"use strict";
const common_vendor = require("../../common/vendor.js");
const _sfc_main = {
data() {
return {
form: { phone: "", password: "" },
showPwd: false,
submitting: false
};
},
methods: {
validatePhone(p) {
return /^1\d{10}$/.test(String(p || ""));
},
validate() {
if (!this.validatePhone(this.form.phone)) {
common_vendor.index.showToast({ title: "请输入有效手机号", icon: "none" });
return false;
}
if (!this.form.password) {
common_vendor.index.showToast({ title: "请输入密码", icon: "none" });
return false;
}
if (this.form.password.length < 6) {
common_vendor.index.showToast({ title: "密码至少6位", icon: "none" });
return false;
}
return true;
},
submit() {
if (this.submitting)
return;
if (!this.validate())
return;
this.submitting = true;
try {
common_vendor.index.setStorageSync("LOGIN_STATUS", "logged_in");
common_vendor.index.setStorageSync("LOGIN_PHONE", this.form.phone);
try {
const uid = common_vendor.index.getStorageSync("DEFAULT_USER_ID") || "";
const enable = common_vendor.index.getStorageSync("ENABLE_DEFAULT_USER") || "";
if (!enable)
common_vendor.index.setStorageSync("ENABLE_DEFAULT_USER", "true");
if (!uid)
common_vendor.index.setStorageSync("DEFAULT_USER_ID", "2");
} catch (e) {
}
common_vendor.index.showToast({ title: "登录成功(本地)", icon: "none" });
setTimeout(() => {
common_vendor.index.reLaunch({ url: "/pages/index/index" });
}, 500);
} finally {
this.submitting = false;
}
}
}
};
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
return {
a: $data.form.phone,
b: common_vendor.o(common_vendor.m(($event) => $data.form.phone = $event.detail.value, {
trim: true
})),
c: !$data.showPwd,
d: $data.form.password,
e: common_vendor.o(common_vendor.m(($event) => $data.form.password = $event.detail.value, {
trim: true
})),
f: common_vendor.t($data.showPwd ? "隐藏" : "显示"),
g: common_vendor.o(($event) => $data.showPwd = !$data.showPwd),
h: $data.submitting,
i: common_vendor.o((...args) => $options.submit && $options.submit(...args))
};
}
const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render]]);
wx.createPage(MiniProgramPage);
//# sourceMappingURL=../../../.sourcemap/mp-weixin/pages/my/password-login.js.map

View File

@@ -1,4 +0,0 @@
{
"navigationBarTitleText": "账号登录",
"usingComponents": {}
}

View File

@@ -1 +0,0 @@
<view class="login"><view class="card"><view class="title">手机号登录</view><view class="field"><text class="label">手机号</text><input class="input" type="number" placeholder="请输入手机号" maxlength="11" value="{{a}}" bindinput="{{b}}"/></view><view class="field"><text class="label">密码</text><input class="input" password="{{c}}" placeholder="请输入密码" maxlength="32" value="{{d}}" bindinput="{{e}}"/><view class="toggle" bindtap="{{g}}">{{f}}</view></view><view class="hint">本页面为静态实现,不调用后端接口,仅做本地校验与存储。</view><button class="primary" disabled="{{h}}" bindtap="{{i}}">登录</button></view></view>

View File

@@ -0,0 +1,196 @@
"use strict";
const common_vendor = require("../../common/vendor.js");
const common_http = require("../../common/http.js");
const common_config = require("../../common/config.js");
const _sfc_main = {
data() {
return {
form: { name: "", avatarUrl: "" },
pwd: { oldPassword: "", newPassword: "" },
phone: { phone: "" },
savingProfile: false,
savingPwd: false,
savingPhone: false,
sendingCode: false,
originalAvatarUrl: ""
};
},
onShow() {
this.loadProfile();
},
computed: {
avatarPreview() {
return this.normalizeAvatar(this.form.avatarUrl);
},
canSendPhone() {
const p = String(this.phone.phone || "").trim();
return /^1\d{10}$/.test(p);
}
},
methods: {
async loadProfile() {
try {
const data = await common_http.get("/api/user/me");
const rawAvatar = (data == null ? void 0 : data.avatarUrl) || (common_vendor.index.getStorageSync("USER_AVATAR_RAW") || "");
this.originalAvatarUrl = rawAvatar;
this.form.name = (data == null ? void 0 : data.name) || (common_vendor.index.getStorageSync("USER_NAME") || "");
this.form.avatarUrl = rawAvatar;
} catch (e) {
}
},
normalizeAvatar(url) {
if (!url)
return "/static/icons/icons8-mitt-24.png";
const s = String(url);
if (/^https?:\/\//i.test(s))
return s;
const base = common_config.API_BASE_URL || "";
if (!base)
return s;
if (s.startsWith("/"))
return `${base}${s}`;
return `${base}/${s}`;
},
openAvatarDialog() {
common_vendor.index.showActionSheet({
itemList: ["粘贴图片URL", "从相册选择并上传"],
success: (res) => {
if (res.tapIndex === 0) {
common_vendor.index.showModal({
title: "头像URL",
editable: true,
placeholderText: "https://...",
success: async (m) => {
if (m.confirm && m.content) {
this.form.avatarUrl = m.content.trim();
await this.saveProfile({ auto: true });
}
}
});
} else if (res.tapIndex === 1) {
common_vendor.index.chooseImage({ count: 1, sizeType: ["compressed"], success: (ci) => {
const filePath = ci.tempFilePaths && ci.tempFilePaths[0] || "";
if (!filePath)
return;
common_vendor.index.showLoading({ title: "上传中..." });
common_http.upload("/api/attachments", filePath, { ownerType: "user_avatar", ownerId: 0 }).then(async (data) => {
const url = data && (data.url || data.path);
if (url) {
this.form.avatarUrl = url;
await this.saveProfile({ auto: true });
}
common_vendor.index.showToast({ title: "已上传", icon: "success" });
}).catch((e) => {
common_vendor.index.showToast({ title: e && e.message || "上传失败", icon: "none" });
}).finally(() => {
common_vendor.index.hideLoading();
});
} });
}
}
});
},
async saveProfile(opts = {}) {
const auto = opts && opts.auto;
const payload = {};
if (this.form.name && this.form.name !== common_vendor.index.getStorageSync("USER_NAME"))
payload.name = this.form.name;
if (this.form.avatarUrl && this.form.avatarUrl !== this.originalAvatarUrl)
payload.avatarUrl = this.form.avatarUrl;
if (Object.keys(payload).length === 0) {
if (!auto)
common_vendor.index.showToast({ title: "无需修改", icon: "none" });
return;
}
if (this.savingProfile)
return;
this.savingProfile = true;
try {
await common_http.put("/api/user/me", payload);
try {
if (payload.name)
common_vendor.index.setStorageSync("USER_NAME", payload.name);
if (payload.avatarUrl) {
const rawUrl = payload.avatarUrl;
const displayUrl = `${rawUrl}${rawUrl.includes("?") ? "&" : "?"}t=${Date.now()}`;
common_vendor.index.setStorageSync("USER_AVATAR_RAW", rawUrl);
common_vendor.index.setStorageSync("USER_AVATAR", rawUrl);
this.originalAvatarUrl = rawUrl;
this.form.avatarUrl = rawUrl;
}
} catch (_) {
}
if (!payload.avatarUrl && this.form.avatarUrl) {
common_vendor.index.setStorageSync("USER_AVATAR_RAW", this.form.avatarUrl);
common_vendor.index.setStorageSync("USER_AVATAR", this.form.avatarUrl);
}
common_vendor.index.showToast({ title: auto ? "头像已更新" : "已保存", icon: "success" });
} catch (e) {
const msg = e && e.message || "保存失败";
common_vendor.index.showToast({ title: msg, icon: "none" });
} finally {
this.savingProfile = false;
}
},
async changePassword() {
if (!this.pwd.newPassword || this.pwd.newPassword.length < 6)
return common_vendor.index.showToast({ title: "新密码至少6位", icon: "none" });
this.savingPwd = true;
try {
await common_http.put("/api/user/me/password", { oldPassword: this.pwd.oldPassword || void 0, newPassword: this.pwd.newPassword });
this.pwd.oldPassword = "";
this.pwd.newPassword = "";
common_vendor.index.showToast({ title: "密码已修改", icon: "success" });
} catch (e) {
common_vendor.index.showToast({ title: e && e.message || "修改失败", icon: "none" });
} finally {
this.savingPwd = false;
}
},
async changePhoneDirect() {
if (!this.canSendPhone)
return common_vendor.index.showToast({ title: "请输入正确手机号", icon: "none" });
this.savingPhone = true;
try {
await common_http.put("/api/user/me/phone", { phone: this.phone.phone });
common_vendor.index.setStorageSync("USER_MOBILE", this.phone.phone);
common_vendor.index.showToast({ title: "手机号已保存", icon: "success" });
} catch (e) {
common_vendor.index.showToast({ title: e && e.message || "保存失败", icon: "none" });
} finally {
this.savingPhone = false;
}
}
}
};
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
return {
a: $options.avatarPreview,
b: common_vendor.o((...args) => $options.openAvatarDialog && $options.openAvatarDialog(...args)),
c: $data.form.name,
d: common_vendor.o(common_vendor.m(($event) => $data.form.name = $event.detail.value, {
trim: true
})),
e: $data.savingProfile,
f: common_vendor.o((...args) => $options.saveProfile && $options.saveProfile(...args)),
g: $data.pwd.oldPassword,
h: common_vendor.o(common_vendor.m(($event) => $data.pwd.oldPassword = $event.detail.value, {
trim: true
})),
i: $data.pwd.newPassword,
j: common_vendor.o(common_vendor.m(($event) => $data.pwd.newPassword = $event.detail.value, {
trim: true
})),
k: $data.savingPwd,
l: common_vendor.o((...args) => $options.changePassword && $options.changePassword(...args)),
m: $data.phone.phone,
n: common_vendor.o(common_vendor.m(($event) => $data.phone.phone = $event.detail.value, {
trim: true
})),
o: $data.savingPhone,
p: common_vendor.o((...args) => $options.changePhoneDirect && $options.changePhoneDirect(...args))
};
}
const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render]]);
wx.createPage(MiniProgramPage);
//# sourceMappingURL=../../../.sourcemap/mp-weixin/pages/my/security.js.map

View File

@@ -0,0 +1,4 @@
{
"navigationBarTitleText": "账号与安全",
"usingComponents": {}
}

View File

@@ -0,0 +1 @@
<view class="security"><view class="card"><view class="cell" bindtap="{{b}}"><text class="cell-label">头像</text><image class="avatar-preview" src="{{a}}" mode="aspectFill"/><text class="arrow"></text></view><view class="cell"><text class="cell-label">姓名</text><input class="cell-input" type="text" placeholder="请输入姓名" value="{{c}}" bindinput="{{d}}"/></view><button class="btn" type="primary" loading="{{e}}" bindtap="{{f}}">保存资料</button></view><view class="card"><view class="row"><text class="label">旧密码</text><input class="input" password placeholder="如从未设置可留空" value="{{g}}" bindinput="{{h}}"/></view><view class="row"><text class="label">新密码</text><input class="input" password placeholder="至少6位" value="{{i}}" bindinput="{{j}}"/></view><button class="btn" loading="{{k}}" bindtap="{{l}}">修改密码</button></view><view class="card"><view class="row"><text class="label">手机号</text><input class="input" type="text" placeholder="11位手机号" value="{{m}}" bindinput="{{n}}"/></view><button class="btn" loading="{{o}}" bindtap="{{p}}">保存手机号</button></view></view>

View File

@@ -24,81 +24,75 @@
/* 垂直间距 */
/* 透明度 */
/* 文章场景相关 */
.sms-login {
.security {
padding: 24rpx;
}
.card {
background: #ffffff;
border-radius: 16rpx;
padding: 28rpx;
padding: 22rpx;
margin-bottom: 24rpx;
box-shadow: 0 6rpx 16rpx rgba(0, 0, 0, 0.08);
}
.title {
font-size: 32rpx;
font-weight: 700;
margin-bottom: 20rpx;
}
.form {
.cell {
display: flex;
flex-direction: column;
align-items: center;
gap: 16rpx;
padding: 20rpx 0;
border-bottom: 1rpx solid #e5e7eb;
}
.cell:last-of-type {
border-bottom: none;
}
.cell-label {
flex: 1;
font-size: 28rpx;
color: #111;
}
.cell-input {
flex: 2;
height: 72rpx;
padding: 0 16rpx;
border: 1rpx solid #e5e7eb;
border-radius: 10rpx;
background: #fff;
color: #111;
}
.avatar-preview {
width: 100rpx;
height: 100rpx;
border-radius: 16rpx;
background: #f1f1f1;
}
.arrow {
margin-left: 12rpx;
color: #99a2b3;
font-size: 32rpx;
}
.row {
display: flex;
gap: 12rpx;
align-items: center;
gap: 16rpx;
margin-bottom: 16rpx;
}
.label {
width: 160rpx;
color: #111;
font-size: 28rpx;
}
.input {
background: #fff;
flex: 1;
height: 72rpx;
padding: 0 16rpx;
border: 1rpx solid #e5e7eb;
border-radius: 12rpx;
padding: 20rpx;
font-size: 28rpx;
flex: 1;
border-radius: 10rpx;
background: #fff;
color: #111;
}
.input.code {
flex: 1;
}
.send {
min-width: 220rpx;
}
.login {
.btn {
margin-top: 8rpx;
}
.hint {
margin-top: 12rpx;
font-size: 22rpx;
color: #444;
}
.debug {
margin-top: 20rpx;
}
.debug-title {
font-size: 26rpx;
color: #444;
}
.debug-body {
margin-top: 12rpx;
display: flex;
flex-direction: column;
gap: 12rpx;
}
.code-title {
font-size: 24rpx;
color: #444;
}
.code-wrap {
position: relative;
background: #fff;
border: 1rpx solid #e5e7eb;
border-radius: 12rpx;
padding: 16rpx;
}
.code {
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
font-size: 24rpx;
white-space: pre-wrap;
}
.copy {
position: absolute;
top: 8rpx;
right: 8rpx;
.btn.minor {
background: #f1f1f1;
color: #111;
}

View File

@@ -1,153 +0,0 @@
"use strict";
const common_vendor = require("../../common/vendor.js");
const common_http = require("../../common/http.js");
const _sfc_main = {
data() {
return { phone: "", code: "", countdown: 0, timer: null, sending: false, logging: false, showDebug: true };
},
computed: {
btnText() {
return this.countdown > 0 ? `${this.countdown}s` : this.sending ? "发送中..." : "获取验证码";
},
trimmedPhone() {
return String(this.phone || "").trim();
},
sendBodyJson() {
return JSON.stringify({ phone: this.trimmedPhone, scene: "login" }, null, 2);
},
loginBodyJson() {
return JSON.stringify({ phone: this.trimmedPhone, code: String(this.code || "").trim() }, null, 2);
}
},
onUnload() {
if (this.timer)
clearInterval(this.timer);
},
methods: {
validatePhone(p) {
return /^1\d{10}$/.test(String(p || "").trim());
},
startCountdown(sec) {
this.countdown = sec;
if (this.timer)
clearInterval(this.timer);
this.timer = setInterval(() => {
if (this.countdown <= 1) {
clearInterval(this.timer);
this.timer = null;
this.countdown = 0;
return;
}
this.countdown--;
}, 1e3);
},
async sendCode() {
if (this.sending || this.countdown > 0)
return;
const p = String(this.phone || "").trim();
if (!this.validatePhone(p))
return common_vendor.index.showToast({ title: "请输入正确的手机号", icon: "none" });
this.sending = true;
try {
const res = await common_http.post("/api/auth/sms/send", { phone: p, scene: "login" });
const cd = Number(res && res.cooldownSec || 60);
this.startCountdown(cd);
common_vendor.index.showToast({ title: "验证码已发送", icon: "none" });
} catch (e) {
const msg = e && e.message || "发送失败";
common_vendor.index.showToast({ title: msg, icon: "none" });
} finally {
this.sending = false;
}
},
async doLogin() {
if (this.logging)
return;
const p = String(this.phone || "").trim();
const c = String(this.code || "").trim();
if (!this.validatePhone(p))
return common_vendor.index.showToast({ title: "请输入正确的手机号", icon: "none" });
if (!/^\d{6}$/.test(c))
return common_vendor.index.showToast({ title: "验证码格式不正确", icon: "none" });
this.logging = true;
try {
const data = await common_http.post("/api/auth/sms/login", { phone: p, code: c });
if (data && data.token) {
common_vendor.index.setStorageSync("TOKEN", data.token);
if (data.user && data.user.phone)
common_vendor.index.setStorageSync("USER_MOBILE", data.user.phone);
common_vendor.index.showToast({ title: "登录成功", icon: "none" });
setTimeout(() => {
common_vendor.index.reLaunch({ url: "/pages/index/index" });
}, 300);
}
} catch (e) {
const msg = e && e.message || "登录失败";
common_vendor.index.showToast({ title: msg, icon: "none" });
} finally {
this.logging = false;
}
},
async quickRegister() {
if (this.logging)
return;
const p = String(this.phone || "").trim();
if (!this.validatePhone(p))
return common_vendor.index.showToast({ title: "请输入正确的手机号", icon: "none" });
this.logging = true;
try {
const data = await common_http.post("/api/auth/register", { phone: p });
if (data && data.token) {
common_vendor.index.setStorageSync("TOKEN", data.token);
if (data.user && data.user.phone)
common_vendor.index.setStorageSync("USER_MOBILE", data.user.phone);
common_vendor.index.showToast({ title: "注册成功", icon: "none" });
setTimeout(() => {
common_vendor.index.reLaunch({ url: "/pages/index/index" });
}, 300);
}
} catch (e) {
const msg = e && e.message || "注册失败";
common_vendor.index.showToast({ title: msg, icon: "none" });
} finally {
this.logging = false;
}
},
copy(text) {
try {
common_vendor.index.setClipboardData({ data: String(text || "") });
common_vendor.index.showToast({ title: "已复制", icon: "none" });
} catch (e) {
}
},
toggleDebug() {
this.showDebug = !this.showDebug;
}
}
};
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
return common_vendor.e({
a: $data.phone,
b: common_vendor.o(($event) => $data.phone = $event.detail.value),
c: $data.code,
d: common_vendor.o(($event) => $data.code = $event.detail.value),
e: common_vendor.t($options.btnText),
f: $data.countdown > 0 || $data.sending,
g: common_vendor.o((...args) => $options.sendCode && $options.sendCode(...args)),
h: $data.logging,
i: common_vendor.o((...args) => $options.doLogin && $options.doLogin(...args)),
j: $data.logging,
k: common_vendor.o((...args) => $options.quickRegister && $options.quickRegister(...args)),
l: common_vendor.t($data.showDebug ? "收起" : "展开"),
m: common_vendor.o((...args) => $options.toggleDebug && $options.toggleDebug(...args)),
n: $data.showDebug
}, $data.showDebug ? {
o: common_vendor.t($options.sendBodyJson),
p: common_vendor.o(($event) => $options.copy($options.sendBodyJson)),
q: common_vendor.t($options.loginBodyJson),
r: common_vendor.o(($event) => $options.copy($options.loginBodyJson))
} : {});
}
const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render]]);
wx.createPage(MiniProgramPage);
//# sourceMappingURL=../../../.sourcemap/mp-weixin/pages/my/sms-login.js.map

View File

@@ -1,4 +0,0 @@
{
"navigationBarTitleText": "短信验证码登录",
"usingComponents": {}
}

View File

@@ -1 +0,0 @@
<view class="page sms-login"><view class="card"><view class="title">短信验证码登录</view><view class="form"><input class="input" type="number" maxlength="11" placeholder="请输入手机号" value="{{a}}" bindinput="{{b}}"/><view class="row"><input class="input code" type="number" maxlength="6" placeholder="请输入验证码" value="{{c}}" bindinput="{{d}}"/><button class="send" disabled="{{f}}" bindtap="{{g}}">{{e}}</button></view><button class="login" type="primary" disabled="{{h}}" bindtap="{{i}}">登录/注册</button><button class="login" disabled="{{j}}" bindtap="{{k}}">注册为店主</button></view><view class="hint">首次登录将自动创建店铺与店主用户。</view><view class="debug"><view class="debug-title" bindtap="{{m}}">请求体示例(点击{{l}}</view><view wx:if="{{n}}" class="debug-body"><view class="code-title">POST /api/auth/sms/send</view><view class="code-wrap"><text class="code">{{o}}</text><button size="mini" class="copy" bindtap="{{p}}">复制</button></view><view class="code-title">POST /api/auth/sms/login</view><view class="code-wrap"><text class="code">{{q}}</text><button size="mini" class="copy" bindtap="{{r}}">复制</button></view></view></view></view></view>

View File

@@ -1,50 +1,87 @@
"use strict";
const common_vendor = require("../../common/vendor.js");
const common_config = require("../../common/config.js");
const common_http = require("../../common/http.js");
const common_assets = require("../../common/assets.js");
const _sfc_main = {
data() {
return {
isVip: false,
expire: "",
price: common_config.VIP_PRICE_PER_MONTH
price: 0,
benefits: []
};
},
onShow() {
this.loadVip();
this.composeBenefits();
},
computed: {
expireDisplay() {
const s = String(this.expire || "");
return s || "11年11月11日";
},
priceDisplay() {
const n = Number(this.price);
return Number.isFinite(n) && n > 0 ? n.toFixed(2) : "0.00";
}
},
methods: {
loadVip() {
composeBenefits() {
this.benefits = [
{ key: "history", title: "完整历史留存", desc: "无限期保留交易、库存与客户数据", icon: "/static/icons/icons8-graph-report-50.png" },
{ key: "analysis", title: "高级统计面板", desc: "秒级汇总销售毛利,掌握生意节奏", icon: "/static/icons/icons8-profit-50.png" },
{ key: "priority", title: "优先客服支持", desc: "遇到问题优先处理,响应更迅速", icon: "/static/icons/icons8-account-male-100.png" }
];
},
async loadVip() {
try {
this.isVip = String(common_vendor.index.getStorageSync("USER_VIP_IS_VIP") || "false").toLowerCase() === "true";
this.expire = common_vendor.index.getStorageSync("USER_VIP_END") || "";
const data = await common_http.get("/api/vip/status");
this.isVip = !!(data == null ? void 0 : data.isVip);
this.expire = (data == null ? void 0 : data.expireAt) || "";
if (typeof (data == null ? void 0 : data.price) === "number")
this.price = data.price;
} catch (e) {
this.isVip = false;
}
},
onPay() {
common_vendor.index.showToast({ title: "静态页面演示:支付功能未接入", icon: "none" });
async onPay() {
try {
await common_http.post("/api/vip/pay", {});
common_vendor.index.showToast({ title: "已开通VIP", icon: "success" });
await this.loadVip();
} catch (e) {
common_vendor.index.showToast({ title: String(e.message || "开通失败"), icon: "none" });
}
}
}
};
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
return common_vendor.e({
a: common_vendor.t($data.isVip ? "VIP会员" : "成为VIP会员"),
b: common_vendor.t($data.isVip ? "尊享专属特权" : "解锁更多权益"),
c: common_vendor.t($data.isVip ? "VIP会员" : "普通用户"),
d: $data.isVip ? 1 : "",
e: $data.isVip
a: common_assets._imports_0$3,
b: common_vendor.t($data.isVip ? "VIP会员" : "升级 VIP 会员"),
c: common_vendor.t($data.isVip ? "尊享完整数据与高效体验" : "开通后可查看全部历史数据并解锁高级功能"),
d: common_vendor.t($data.isVip ? "已开通" : "普通用户"),
e: $data.isVip ? 1 : "",
f: $data.isVip
}, $data.isVip ? {
f: common_vendor.t($options.expireDisplay)
} : {}, {
g: !$data.isVip
g: common_vendor.t($options.expireDisplay)
} : {
h: common_vendor.t($options.priceDisplay)
}, {
i: common_vendor.f($data.benefits, (item, k0, i0) => {
return common_vendor.e({
a: item.icon
}, item.icon ? {
b: item.icon
} : {}, {
c: common_vendor.t(item.title),
d: common_vendor.t(item.desc),
e: item.key
});
}),
j: !$data.isVip
}, !$data.isVip ? {
h: common_vendor.t($data.price),
i: common_vendor.o((...args) => $options.onPay && $options.onPay(...args))
k: common_vendor.o((...args) => $options.onPay && $options.onPay(...args))
} : {});
}
const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render]]);

View File

@@ -1 +1 @@
<view class="vip-page" style="background:linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%);min-height:100vh"><view class="main-content"><view class="vip-header"><view class="vip-crown"><text class="crown-icon">👑</text></view><text class="vip-title">{{a}}</text><text class="vip-subtitle">{{b}}</text><view class="{{['vip-status', d && 'active']}}"><text class="status-text">{{c}}</text></view></view><view class="features-section"><text class="section-title">会员功能</text><view class="feature-card"><view class="feature-icon">💾</view><text class="feature-text">永久存储数据</text></view></view><view wx:if="{{e}}" class="vip-info"><view class="info-card"><text class="info-label">会员状态</text><text class="info-value active">已激活</text></view><view class="info-card"><text class="info-label">有效期至</text><text class="info-value">{{f}}</text></view></view><view wx:if="{{g}}" class="purchase-section"><view class="price-card"><text class="price-label">会员价格</text><view class="price-display"><text class="price-symbol">¥</text><text class="price-amount">{{h}}</text><text class="price-period">/月</text></view></view><button class="purchase-btn" bindtap="{{i}}"><text class="btn-text">立即开通VIP</text></button></view></view><view class="bg-decoration"><view class="decoration-circle circle-1"></view><view class="decoration-circle circle-2"></view><view class="decoration-circle circle-3"></view></view></view>
<view class="vip-page"><view class="vip-hero"><image class="hero-icon" src="{{a}}" mode="aspectFit"/><view class="hero-text"><text class="hero-title">{{b}}</text><text class="hero-subtitle">{{c}}</text></view><view class="{{['status-pill', e && 'active']}}"><text>{{d}}</text></view></view><view wx:if="{{f}}" class="vip-summary"><view class="summary-item"><text class="summary-label">会员状态</text><text class="summary-value success">已激活</text></view><view class="summary-item"><text class="summary-label">有效期至</text><text class="summary-value">{{g}}</text></view></view><view wx:else class="vip-summary"><view class="summary-item"><text class="summary-label">当前身份</text><text class="summary-value">普通用户</text></view><view class="summary-item"><text class="summary-label">会员价格</text><text class="summary-value highlight">¥{{h}}/月</text></view></view><view class="benefit-section"><view class="section-header"><text class="section-title">会员特权</text><text class="section-subtitle">聚焦数据留存与专业形象,让经营更有底气</text></view><view class="benefit-grid"><view wx:for="{{i}}" wx:for-item="item" wx:key="e" class="benefit-card"><image wx:if="{{item.a}}" src="{{item.b}}" class="benefit-icon" mode="aspectFit"/><text class="benefit-title">{{item.c}}</text><text class="benefit-desc">{{item.d}}</text></view></view></view><view wx:if="{{j}}" class="purchase-card"><view class="purchase-text"><text class="purchase-title">立即升级 VIP</text><text class="purchase-desc">不限历史数据、专属标识,助您高效管账</text></view><button class="purchase-btn" bindtap="{{k}}"><text>立即开通</text></button></view></view>

View File

@@ -25,240 +25,207 @@
/* 透明度 */
/* 文章场景相关 */
page {
background: #1a1a2e !important;
background: linear-gradient(180deg, #f8fbff 0%, #ffffff 60%) !important;
}
.vip-page {
min-height: 100vh;
width: 100%;
position: relative;
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%) !important;
overflow: hidden;
padding: 32rpx 24rpx 120rpx;
box-sizing: border-box;
display: flex;
flex-direction: column;
gap: 28rpx;
}
.main-content {
flex: 1;
padding: 60rpx 40rpx 40rpx;
position: relative;
z-index: 10;
}
/* VIP头部区域 */
.vip-header {
text-align: center;
margin-bottom: 80rpx;
}
.vip-header .vip-crown {
margin-bottom: 30rpx;
}
.vip-header .vip-crown .crown-icon {
font-size: 80rpx;
filter: drop-shadow(0 4rpx 12rpx rgba(255, 215, 0, 0.3));
}
.vip-header .vip-title {
display: block;
font-size: 48rpx;
font-weight: 700;
color: #fff;
margin-bottom: 16rpx;
text-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
}
.vip-header .vip-subtitle {
display: block;
font-size: 28rpx;
color: rgba(255, 255, 255, 0.8);
margin-bottom: 40rpx;
}
.vip-header .vip-status {
display: inline-block;
padding: 16rpx 32rpx;
border-radius: 50rpx;
background: rgba(0, 0, 0, 0.2);
-webkit-backdrop-filter: blur(10rpx);
backdrop-filter: blur(10rpx);
border: 1rpx solid rgba(255, 215, 0, 0.4);
}
.vip-header .vip-status.active {
background: linear-gradient(45deg, #ffd700, #ffed4e);
border: 1rpx solid rgba(255, 215, 0, 0.3);
}
.vip-header .vip-status.active .status-text {
color: #333;
}
.vip-header .vip-status .status-text {
font-size: 26rpx;
font-weight: 600;
color: #fff;
}
/* 会员功能区域 */
.features-section {
margin-bottom: 60rpx;
}
.features-section .section-title {
display: block;
font-size: 36rpx;
font-weight: 600;
color: #fff;
text-align: center;
margin-bottom: 40rpx;
}
.features-section .feature-card {
background: rgba(0, 0, 0, 0.15);
-webkit-backdrop-filter: blur(15rpx);
backdrop-filter: blur(15rpx);
border-radius: 24rpx;
padding: 40rpx;
text-align: center;
border: 1rpx solid rgba(255, 215, 0, 0.3);
transition: all 0.3s ease;
}
.features-section .feature-card:hover {
transform: translateY(-4rpx);
box-shadow: 0 12rpx 40rpx rgba(0, 0, 0, 0.15);
}
.features-section .feature-card .feature-icon {
font-size: 60rpx;
margin-bottom: 24rpx;
filter: drop-shadow(0 4rpx 12rpx rgba(255, 215, 0, 0.3));
}
.features-section .feature-card .feature-text {
display: block;
font-size: 32rpx;
font-weight: 600;
color: #fff;
letter-spacing: 1rpx;
}
/* VIP信息卡片 */
.vip-info {
margin-bottom: 60rpx;
}
.vip-info .info-card {
background: rgba(0, 0, 0, 0.15);
-webkit-backdrop-filter: blur(15rpx);
backdrop-filter: blur(15rpx);
border-radius: 20rpx;
padding: 32rpx;
margin-bottom: 20rpx;
border: 1rpx solid rgba(255, 215, 0, 0.3);
.vip-hero {
display: flex;
justify-content: space-between;
align-items: center;
}
.vip-info .info-card .info-label {
font-size: 28rpx;
color: rgba(255, 255, 255, 0.8);
}
.vip-info .info-card .info-value {
font-size: 30rpx;
font-weight: 600;
color: #fff;
}
.vip-info .info-card .info-value.active {
color: #ffd700;
}
/* 购买区域 */
.purchase-section .price-card {
background: rgba(0, 0, 0, 0.15);
-webkit-backdrop-filter: blur(15rpx);
backdrop-filter: blur(15rpx);
gap: 20rpx;
padding: 26rpx 28rpx;
border-radius: 24rpx;
padding: 40rpx;
text-align: center;
margin-bottom: 40rpx;
border: 1rpx solid rgba(255, 215, 0, 0.3);
background: rgba(255, 255, 255, 0.98);
border: 2rpx solid #edf2f9;
box-shadow: 0 10rpx 30rpx rgba(76, 141, 255, 0.12);
}
.purchase-section .price-card .price-label {
display: block;
font-size: 28rpx;
color: rgba(255, 255, 255, 0.8);
margin-bottom: 16rpx;
.hero-icon {
width: 88rpx;
height: 88rpx;
border-radius: 24rpx;
background: #f0f6ff;
padding: 12rpx;
}
.purchase-section .price-card .price-display {
.hero-text {
flex: 1;
display: flex;
align-items: baseline;
justify-content: center;
flex-direction: column;
gap: 8rpx;
}
.purchase-section .price-card .price-display .price-symbol {
font-size: 32rpx;
color: #ffd700;
font-weight: 600;
.hero-title {
font-size: 36rpx;
font-weight: 800;
color: #4C8DFF;
letter-spacing: 1rpx;
}
.purchase-section .price-card .price-display .price-amount {
font-size: 60rpx;
.hero-subtitle {
font-size: 26rpx;
color: #5175b5;
line-height: 36rpx;
}
.status-pill {
flex: 0 0 auto;
padding: 12rpx 20rpx;
border-radius: 999rpx;
background: #e6edfb;
color: #4463a6;
font-size: 24rpx;
font-weight: 700;
color: #ffd700;
border: 2rpx solid rgba(76, 141, 255, 0.2);
}
.purchase-section .price-card .price-display .price-period {
.status-pill.active {
background: #4c8dff;
color: #fff;
border-color: #4c8dff;
}
.vip-summary {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 16rpx;
background: rgba(255, 255, 255, 0.98);
padding: 24rpx;
border-radius: 24rpx;
border: 2rpx solid #eef3fb;
box-shadow: 0 8rpx 24rpx rgba(99, 132, 191, 0.1);
}
.summary-item {
background: #f6f9ff;
border-radius: 18rpx;
padding: 22rpx 24rpx;
display: flex;
flex-direction: column;
gap: 12rpx;
border: 2rpx solid rgba(76, 141, 255, 0.12);
}
.summary-label {
font-size: 24rpx;
color: #5f7394;
}
.summary-value {
font-size: 30rpx;
font-weight: 700;
color: #1f2c3d;
}
.summary-value.success {
color: #1ead91;
}
.summary-value.highlight {
color: #2f58d1;
}
.benefit-section {
background: rgba(255, 255, 255, 0.98);
border-radius: 24rpx;
padding: 28rpx;
border: 2rpx solid #edf2f9;
box-shadow: 0 12rpx 28rpx rgba(32, 75, 143, 0.1);
display: flex;
flex-direction: column;
gap: 24rpx;
}
.section-header {
display: flex;
flex-direction: column;
gap: 8rpx;
}
.section-title {
font-size: 34rpx;
font-weight: 800;
color: #111;
}
.section-subtitle {
font-size: 24rpx;
color: #5f7394;
line-height: 34rpx;
}
.benefit-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 20rpx;
}
.benefit-card {
background: #f7faff;
border-radius: 20rpx;
padding: 24rpx 20rpx;
border: 2rpx solid rgba(76, 141, 255, 0.12);
box-shadow: 0 8rpx 20rpx rgba(0, 0, 0, 0.04);
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 14rpx;
}
.benefit-icon {
width: 48rpx;
height: 48rpx;
}
.benefit-title {
font-size: 28rpx;
color: rgba(255, 255, 255, 0.8);
font-weight: 700;
color: #111;
}
.purchase-section .purchase-btn {
width: 100%;
height: 96rpx;
background: linear-gradient(45deg, #ffd700, #ffed4e);
border-radius: 50rpx;
border: none;
box-shadow: 0 8rpx 24rpx rgba(255, 215, 0, 0.3);
transition: all 0.3s ease;
.benefit-desc {
font-size: 24rpx;
line-height: 34rpx;
color: #5f7394;
}
.purchase-section .purchase-btn:active {
transform: translateY(2rpx);
box-shadow: 0 4rpx 16rpx rgba(255, 215, 0, 0.4);
.purchase-card {
margin-top: auto;
background: linear-gradient(135deg, rgba(76, 141, 255, 0.14) 0%, rgba(76, 141, 255, 0.06) 100%);
border-radius: 28rpx;
padding: 30rpx 28rpx;
display: flex;
align-items: center;
gap: 24rpx;
border: 2rpx solid rgba(76, 141, 255, 0.18);
box-shadow: 0 10rpx 24rpx rgba(76, 141, 255, 0.15);
}
.purchase-section .purchase-btn .btn-text {
.purchase-text {
flex: 1;
display: flex;
flex-direction: column;
gap: 10rpx;
}
.purchase-title {
font-size: 32rpx;
font-weight: 600;
color: #333;
font-weight: 800;
color: #4C8DFF;
}
/* 背景装饰 */
.bg-decoration {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
pointer-events: none;
z-index: 1;
.purchase-desc {
font-size: 24rpx;
color: #4463a6;
line-height: 34rpx;
}
.bg-decoration .decoration-circle {
position: absolute;
border-radius: 50%;
background: rgba(255, 255, 255, 0.05);
.purchase-btn {
flex: 0 0 auto;
padding: 20rpx 36rpx;
border-radius: 999rpx;
border: none;
background: linear-gradient(135deg, #4788ff 0%, #2d6be6 100%);
color: #fff;
font-size: 28rpx;
font-weight: 700;
box-shadow: 0 10rpx 22rpx rgba(45, 107, 230, 0.2);
}
.bg-decoration .decoration-circle.circle-1 {
width: 300rpx;
height: 300rpx;
top: -150rpx;
right: -100rpx;
.purchase-btn:active {
opacity: 0.88;
}
.bg-decoration .decoration-circle.circle-2 {
width: 200rpx;
height: 200rpx;
bottom: 200rpx;
left: -100rpx;
}
.bg-decoration .decoration-circle.circle-3 {
width: 150rpx;
height: 150rpx;
top: 50%;
right: 50rpx;
transform: translateY(-50%);
}
/* 响应式调整 */
@media (max-width: 375px) {
.benefits-grid {
.vip-summary {
grid-template-columns: 1fr;
}
.vip-header .vip-title {
font-size: 42rpx;
.benefit-grid {
grid-template-columns: 1fr;
}
.main-content {
padding: 40rpx 30rpx;
.purchase-card {
flex-direction: column;
align-items: stretch;
}
.status-pill {
display: none;
}
}

View File

@@ -37,14 +37,14 @@ const _sfc_main = {
showMore: false,
SEG_ICONS: {
sale: {
out: "/static/icons/sale.png",
return: "/static/icons/other-pay.png",
collect: "/static/icons/report.png"
out: "/static/icons/icons8-shopping-cart-100.png",
return: "/static/icons/icons8-return-purchase-50.png",
collect: "/static/icons/icons8-profit-50.png"
},
purchase: {
in: "/static/icons/purchase.png",
return: "/static/icons/other-pay.png",
pay: "/static/icons/account.png"
in: "/static/icons/icons8-purchase-order-100.png",
return: "/static/icons/icons8-return-purchase-50.png",
pay: "/static/icons/icons8-dollar-ethereum-exchange-50.png"
}
}
};
@@ -120,13 +120,33 @@ const _sfc_main = {
}
},
methods: {
fixMojibake(s) {
try {
if (!s)
return s;
const bad = /[ÂÃæåé¼½¢]/.test(s);
if (!bad)
return s;
return decodeURIComponent(escape(s));
} catch (_) {
return s;
}
},
normalizeCats(list) {
if (!Array.isArray(list))
return [];
return list.map((it) => ({
key: it && it.key || "",
label: this.fixMojibake(it && it.label || "")
})).filter((it) => it.key && it.label);
},
async fetchCategories() {
try {
const res = await common_http.get("/api/finance/categories");
if (res && Array.isArray(res.incomeCategories))
this._incomeCategories = res.incomeCategories;
this._incomeCategories = this.normalizeCats(res.incomeCategories);
if (res && Array.isArray(res.expenseCategories))
this._expenseCategories = res.expenseCategories;
this._expenseCategories = this.normalizeCats(res.expenseCategories);
this.ensureActiveCategory();
} catch (_) {
this.ensureActiveCategory();
@@ -392,10 +412,8 @@ function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
}, {
ag: $data.biz === "sale" || $data.biz === "purchase",
aG: !$data.items.length
}, !$data.items.length ? {
aH: common_assets._imports_0
} : {
aI: common_vendor.f($data.items, (it, idx, i0) => {
}, !$data.items.length ? {} : {
aH: common_vendor.f($data.items, (it, idx, i0) => {
return {
a: common_vendor.t(it.productName),
b: common_vendor.o([common_vendor.m(($event) => it.quantity = $event.detail.value, {
@@ -411,8 +429,8 @@ function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
};
})
}, {
aJ: common_vendor.o((...args) => $options.saveAndReset && $options.saveAndReset(...args)),
aK: common_vendor.o((...args) => $options.submit && $options.submit(...args))
aI: common_vendor.o((...args) => $options.saveAndReset && $options.saveAndReset(...args)),
aJ: common_vendor.o((...args) => $options.submit && $options.submit(...args))
});
}
const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render]]);

File diff suppressed because one or more lines are too long

View File

@@ -47,9 +47,8 @@
margin: 12rpx 16rpx;
padding: 6rpx;
background: #fff;
border: 2rpx solid #e6ebf2;
border-radius: 999rpx;
box-shadow: 0 6rpx 16rpx rgba(0, 0, 0, 0.04);
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.04);
}
.seg3-item {
flex: 1;
@@ -60,13 +59,14 @@
padding: 12rpx 0;
color: #111;
border-radius: 999rpx;
transition: box-shadow 0.2s ease, background 0.2s ease;
}
/* 间隔通过内边距处理,避免空选择器 */
.seg3-item.active {
background: #fff;
color: #4C8DFF;
box-shadow: 0 4rpx 12rpx rgba(76, 141, 255, 0.2), 0 0 0 2rpx #4C8DFF inset;
box-shadow: 0 3rpx 10rpx rgba(76, 141, 255, 0.16);
}
.seg3-icon {
width: 28rpx;
@@ -75,16 +75,20 @@
}
.field {
display: flex;
justify-content: space-between;
align-items: center;
justify-content: flex-start;
padding: 22rpx 24rpx;
background: #ffffff;
border-bottom: 1rpx solid #e5e7eb;
background: #f8faff;
gap: 16rpx;
}
.label {
width: 160rpx;
color: #444;
}
.value {
flex: 1;
color: #111;
text-align: right;
}
/* 汇总卡片:白底卡片+主色按钮 */
@@ -92,28 +96,27 @@
display: flex;
justify-content: space-between;
align-items: center;
padding: 16rpx 18rpx;
margin: 12rpx 16rpx;
background: #fff;
border: 2rpx solid #e5e7eb;
border-radius: 16rpx;
padding: 18rpx 20rpx;
margin: 16rpx 18rpx 10rpx;
background: none;
border-radius: 18rpx;
color: #111;
}
/* 加号改为图标按钮 */
.add {
margin: 18rpx auto;
margin: 24rpx auto 18rpx;
width: 120rpx;
height: 120rpx;
border-radius: 24rpx;
background: #fff;
border: 2rpx solid #4C8DFF;
border-radius: 28rpx;
background: none;
border: 0;
color: #4C8DFF;
font-size: 72rpx;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 6rpx 16rpx rgba(76, 141, 255, 0.12);
box-shadow: none;
}
.empty {
display: flex;
@@ -131,18 +134,17 @@
}
.list {
background: #fff;
margin: 0 16rpx 12rpx;
border: 2rpx solid #e5e7eb;
border-radius: 16rpx;
margin: 0 18rpx 20rpx;
border-radius: 18rpx;
overflow: hidden;
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.06);
}
.row {
display: grid;
grid-template-columns: 1.5fr 1fr 1fr 1fr;
gap: 12rpx;
padding: 16rpx 12rpx;
padding: 18rpx 16rpx;
align-items: center;
border-bottom: 1rpx solid #e5e7eb;
}
.col.name {
padding-left: 12rpx;
@@ -158,16 +160,30 @@
right: 0;
bottom: 0;
background: #ffffff;
padding: 16rpx 24rpx calc(env(safe-area-inset-bottom) + 16rpx);
padding: 6rpx 18rpx calc(env(safe-area-inset-bottom) + 2rpx);
box-shadow: 0 -4rpx 12rpx rgba(0, 0, 0, 0.16);
}
.primary {
.order .bottom button {
margin: 0;
}
/* 仅限开单页底部按钮样式(缩小高度) */
.order .bottom .primary {
width: 100%;
background: #4C8DFF;
color: #fff;
border-radius: 999rpx;
padding: 20rpx 0;
font-weight: 800;
padding: 14rpx 0;
font-weight: 700;
font-size: 28rpx;
}
.order .bottom .ghost {
background: transparent;
color: #4C8DFF;
border: 0;
border-radius: 999rpx;
padding: 12rpx 0;
font-size: 28rpx;
}
/* 收款/付款页样式 */
@@ -178,17 +194,16 @@
.textarea {
position: relative;
padding: 16rpx 24rpx;
background: #ffffff;
border-top: 1rpx solid #e5e7eb;
background: #f8faff;
}
.amount-badge {
position: absolute;
right: 24rpx;
top: -36rpx;
top: -32rpx;
background: #4C8DFF;
color: #fff;
padding: 8rpx 16rpx;
border-radius: 12rpx;
padding: 10rpx 20rpx;
border-radius: 14rpx;
font-size: 24rpx;
}
.date-mini {
@@ -201,20 +216,21 @@
/* 分类chips样式选中后文字变红 */
.chips {
display: flex;
flex-wrap: wrap;
gap: 12rpx;
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 12rpx 16rpx;
padding: 12rpx 24rpx;
}
.chip {
padding: 10rpx 20rpx;
padding: 12rpx 20rpx;
border-radius: 999rpx;
background: #f1f1f1;
color: #444;
text-align: center;
}
.chip.active {
background: #4C8DFF;
color: #fff;
background: rgba(76, 141, 255, 0.15);
color: #4C8DFF;
}
/* 顶部业务 Tabs 显示 */
@@ -231,10 +247,10 @@
}
.info-field {
background: #fff;
border: 2rpx solid #e6ebf2;
border-radius: 12rpx;
padding: 10rpx 12rpx;
box-shadow: 0 6rpx 16rpx rgba(0, 0, 0, 0.04);
border: 0;
border-radius: 14rpx;
padding: 12rpx 14rpx;
box-shadow: 0 6rpx 16rpx rgba(0, 0, 0, 0.06);
}
.info-label {
color: #444;
@@ -245,17 +261,20 @@
color: #111;
font-weight: 700;
}
.info-action {
/* 缩小“加商品”按钮尺寸,仅在本页卡片内 */
.order .info-card .info-action {
display: flex;
align-items: center;
gap: 6rpx;
background: #4C8DFF;
color: #fff;
border-radius: 12rpx;
padding: 14rpx 16rpx;
box-shadow: 0 8rpx 18rpx rgba(76, 141, 255, 0.26);
padding: 8rpx 12rpx;
box-shadow: 0 5rpx 12rpx rgba(76, 141, 255, 0.18);
font-size: 26rpx;
}
.info-icon {
width: 32rpx;
height: 32rpx;
.order .info-card .info-icon {
width: 24rpx;
height: 24rpx;
}

View File

@@ -24,15 +24,22 @@ const _sfc_main = {
wholesalePrice: null,
bigClientPrice: null,
images: [],
remark: ""
remark: "",
platformStatus: "",
sourceSubmissionId: ""
},
units: [],
categories: []
categories: [],
keyboardHeight: 0
};
},
onLoad(query) {
this.id = (query == null ? void 0 : query.id) || "";
this.bootstrap();
this.initKeyboardListener();
},
onUnload() {
this.disposeKeyboardListener();
},
computed: {
unitNames() {
@@ -56,6 +63,24 @@ const _sfc_main = {
if (this.id)
this.loadDetail();
},
initKeyboardListener() {
try {
this.__keyboardListener = (e) => {
const h = e && (e.height || e.targetHeight || 0) || 0;
this.keyboardHeight = h;
};
common_vendor.index.onKeyboardHeightChange && common_vendor.index.onKeyboardHeightChange(this.__keyboardListener);
} catch (_) {
}
},
disposeKeyboardListener() {
try {
if (this.__keyboardListener && common_vendor.index.offKeyboardHeightChange) {
common_vendor.index.offKeyboardHeightChange(this.__keyboardListener);
}
} catch (_) {
}
},
async fetchUnits() {
try {
const res = await common_http.get("/api/product-units");
@@ -80,10 +105,27 @@ const _sfc_main = {
const c = this.categories[idx];
this.form.categoryId = c ? c.id : "";
},
scan() {
common_vendor.index.scanCode({ onlyFromCamera: false, success: (res) => {
this.form.barcode = res.result;
} });
async chooseAndScanBarcode() {
try {
const chooseRes = await common_vendor.index.chooseImage({ count: 1, sourceType: ["camera", "album"], sizeType: ["compressed"] });
let filePath = chooseRes.tempFilePaths[0];
try {
const comp = await common_vendor.index.compressImage({ src: filePath, quality: 80 });
filePath = comp.tempFilePath || filePath;
} catch (e) {
}
const data = await common_http.upload("/api/barcode/scan", filePath, {}, "file");
if (data && data.success && data.barcode) {
this.form.barcode = data.barcode;
common_vendor.index.showToast({ title: "识别成功", icon: "success", mask: false });
return;
}
const msg = data && (data.message || data.error || data.msg) || "未识别";
common_vendor.index.showToast({ title: msg, icon: "none", mask: false });
} catch (e) {
const msg = e && e.message ? String(e.message) : "网络异常或服务不可用";
common_vendor.index.showToast({ title: msg, icon: "none", mask: false });
}
},
async loadDetail() {
try {
@@ -104,7 +146,10 @@ const _sfc_main = {
retailPrice: data.retailPrice,
wholesalePrice: data.wholesalePrice,
bigClientPrice: data.bigClientPrice,
images: (data.images || []).map((i) => i.url || i)
images: (data.images || []).map((i) => i.url || i),
remark: data.remark || "",
platformStatus: data.platformStatus || "",
sourceSubmissionId: data.sourceSubmissionId || ""
});
} catch (_) {
}
@@ -145,6 +190,10 @@ const _sfc_main = {
};
},
async save(goOn) {
try {
common_vendor.index.hideKeyboard && common_vendor.index.hideKeyboard();
} catch (_) {
}
if (!this.validate())
return;
const payload = this.buildPayload();
@@ -153,14 +202,14 @@ const _sfc_main = {
await common_http.put("/api/products/" + this.id, payload);
else
await common_http.post("/api/products", payload);
common_vendor.index.showToast({ title: "保存成功", icon: "success" });
common_vendor.index.showToast({ title: "保存成功", icon: "success", mask: false });
if (goOn && !this.id) {
this.form = { name: "", barcode: "", brand: "", model: "", spec: "", origin: "", categoryId: "", unitId: "", stock: null, safeMin: null, safeMax: null, purchasePrice: null, retailPrice: null, wholesalePrice: null, bigClientPrice: null, images: [], remark: "" };
this.form = { name: "", barcode: "", brand: "", model: "", spec: "", origin: "", categoryId: "", unitId: "", stock: null, safeMin: null, safeMax: null, purchasePrice: null, retailPrice: null, wholesalePrice: null, bigClientPrice: null, images: [], remark: "", platformStatus: "", sourceSubmissionId: "" };
} else {
setTimeout(() => common_vendor.index.navigateBack(), 400);
}
} catch (e) {
common_vendor.index.showToast({ title: "保存失败", icon: "none" });
common_vendor.index.showToast({ title: "保存失败", icon: "none", mask: false });
}
}
}
@@ -170,79 +219,84 @@ if (!Array) {
_component_ImageUploader();
}
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
return {
a: $data.form.name,
b: common_vendor.o(common_vendor.m(($event) => $data.form.name = $event.detail.value, {
return common_vendor.e({
a: $data.form.platformStatus === "platform"
}, $data.form.platformStatus === "platform" ? {} : $data.form.sourceSubmissionId ? {} : {}, {
b: $data.form.sourceSubmissionId,
c: $data.form.name,
d: common_vendor.o(common_vendor.m(($event) => $data.form.name = $event.detail.value, {
trim: true
})),
c: $data.form.barcode,
d: common_vendor.o(common_vendor.m(($event) => $data.form.barcode = $event.detail.value, {
e: $data.form.barcode,
f: common_vendor.o(common_vendor.m(($event) => $data.form.barcode = $event.detail.value, {
trim: true
})),
e: $data.form.brand,
f: common_vendor.o(common_vendor.m(($event) => $data.form.brand = $event.detail.value, {
g: common_vendor.o((...args) => $options.chooseAndScanBarcode && $options.chooseAndScanBarcode(...args)),
h: $data.form.brand,
i: common_vendor.o(common_vendor.m(($event) => $data.form.brand = $event.detail.value, {
trim: true
})),
g: $data.form.model,
h: common_vendor.o(common_vendor.m(($event) => $data.form.model = $event.detail.value, {
j: $data.form.model,
k: common_vendor.o(common_vendor.m(($event) => $data.form.model = $event.detail.value, {
trim: true
})),
i: $data.form.spec,
j: common_vendor.o(common_vendor.m(($event) => $data.form.spec = $event.detail.value, {
l: $data.form.spec,
m: common_vendor.o(common_vendor.m(($event) => $data.form.spec = $event.detail.value, {
trim: true
})),
k: $data.form.origin,
l: common_vendor.o(common_vendor.m(($event) => $data.form.origin = $event.detail.value, {
n: $data.form.origin,
o: common_vendor.o(common_vendor.m(($event) => $data.form.origin = $event.detail.value, {
trim: true
})),
m: common_vendor.t($options.unitLabel),
n: $options.unitNames,
o: common_vendor.o((...args) => $options.onPickUnit && $options.onPickUnit(...args)),
p: common_vendor.t($options.categoryLabel),
q: $options.categoryNames,
r: common_vendor.o((...args) => $options.onPickCategory && $options.onPickCategory(...args)),
s: $data.form.stock,
t: common_vendor.o(common_vendor.m(($event) => $data.form.stock = $event.detail.value, {
p: common_vendor.t($options.unitLabel),
q: $options.unitNames,
r: common_vendor.o((...args) => $options.onPickUnit && $options.onPickUnit(...args)),
s: common_vendor.t($options.categoryLabel),
t: $options.categoryNames,
v: common_vendor.o((...args) => $options.onPickCategory && $options.onPickCategory(...args)),
w: $data.form.stock,
x: common_vendor.o(common_vendor.m(($event) => $data.form.stock = $event.detail.value, {
number: true
})),
v: $data.form.safeMin,
w: common_vendor.o(common_vendor.m(($event) => $data.form.safeMin = $event.detail.value, {
y: $data.form.safeMin,
z: common_vendor.o(common_vendor.m(($event) => $data.form.safeMin = $event.detail.value, {
number: true
})),
x: $data.form.safeMax,
y: common_vendor.o(common_vendor.m(($event) => $data.form.safeMax = $event.detail.value, {
A: $data.form.safeMax,
B: common_vendor.o(common_vendor.m(($event) => $data.form.safeMax = $event.detail.value, {
number: true
})),
z: $data.form.purchasePrice,
A: common_vendor.o(common_vendor.m(($event) => $data.form.purchasePrice = $event.detail.value, {
C: $data.form.purchasePrice,
D: common_vendor.o(common_vendor.m(($event) => $data.form.purchasePrice = $event.detail.value, {
number: true
})),
B: $data.form.retailPrice,
C: common_vendor.o(common_vendor.m(($event) => $data.form.retailPrice = $event.detail.value, {
E: $data.form.retailPrice,
F: common_vendor.o(common_vendor.m(($event) => $data.form.retailPrice = $event.detail.value, {
number: true
})),
D: $data.form.wholesalePrice,
E: common_vendor.o(common_vendor.m(($event) => $data.form.wholesalePrice = $event.detail.value, {
G: $data.form.wholesalePrice,
H: common_vendor.o(common_vendor.m(($event) => $data.form.wholesalePrice = $event.detail.value, {
number: true
})),
F: $data.form.bigClientPrice,
G: common_vendor.o(common_vendor.m(($event) => $data.form.bigClientPrice = $event.detail.value, {
I: $data.form.bigClientPrice,
J: common_vendor.o(common_vendor.m(($event) => $data.form.bigClientPrice = $event.detail.value, {
number: true
})),
H: common_vendor.o(($event) => $data.form.images = $event),
I: common_vendor.p({
K: common_vendor.o(($event) => $data.form.images = $event),
L: common_vendor.p({
formData: {
ownerType: "product"
},
modelValue: $data.form.images
}),
J: $data.form.remark,
K: common_vendor.o(common_vendor.m(($event) => $data.form.remark = $event.detail.value, {
M: $data.form.remark,
N: common_vendor.o(common_vendor.m(($event) => $data.form.remark = $event.detail.value, {
trim: true
})),
L: common_vendor.o(($event) => $options.save(false)),
M: common_vendor.o(($event) => $options.save(true))
};
O: common_vendor.o(($event) => $options.save(false)),
P: common_vendor.o(($event) => $options.save(true)),
Q: ($data.keyboardHeight || 0) + "px"
});
}
const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render]]);
wx.createPage(MiniProgramPage);

View File

@@ -1 +1 @@
<scroll-view scroll-y class="page"><view class="hero small"><text class="title">编辑货品</text><text class="sub">完善基础信息与价格</text></view><view class="card"><view class="row"><text class="label">商品名称</text><input placeholder="必填" value="{{a}}" bindinput="{{b}}"/></view><view class="row"><text class="label">条形码</text><input placeholder="可扫码或输入" value="{{c}}" bindinput="{{d}}"/></view><view class="row"><text class="label">品牌/型号/规格/产地</text></view><view class="row"><input placeholder="品牌" value="{{e}}" bindinput="{{f}}"/></view><view class="row"><input placeholder="型号" value="{{g}}" bindinput="{{h}}"/></view><view class="row"><input placeholder="规格" value="{{i}}" bindinput="{{j}}"/></view><view class="row"><input placeholder="产地" value="{{k}}" bindinput="{{l}}"/></view><view class="row"><picker mode="selector" range="{{n}}" bindchange="{{o}}"><view class="picker">主单位:{{m}}</view></picker><picker mode="selector" range="{{q}}" bindchange="{{r}}"><view class="picker">类别:{{p}}</view></picker></view></view><view class="card"><view class="row"><text class="label">库存与安全库存</text></view><view class="row"><input type="number" placeholder="当前库存" value="{{s}}" bindinput="{{t}}"/><input type="number" placeholder="安全库存下限" value="{{v}}" bindinput="{{w}}"/><input type="number" placeholder="安全库存上限" value="{{x}}" bindinput="{{y}}"/></view></view><view class="card"><view class="row"><text class="label">价格(进价/零售/批发/大单)</text></view><view class="row prices"><input type="number" placeholder="进货价" value="{{z}}" bindinput="{{A}}"/><input type="number" placeholder="零售价" value="{{B}}" bindinput="{{C}}"/><input type="number" placeholder="批发价" value="{{D}}" bindinput="{{E}}"/><input type="number" placeholder="大单价" value="{{F}}" bindinput="{{G}}"/></view></view><view class="card"><text class="label">图片</text><image-uploader wx:if="{{I}}" u-i="4a3f460a-0" bind:__l="__l" bindupdateModelValue="{{H}}" u-p="{{I}}"/></view><view class="card"><text class="label">备注</text><block wx:if="{{r0}}"><textarea placeholder="可选" auto-height value="{{J}}" bindinput="{{K}}"/></block></view><view class="fixed"><button class="ghost" bindtap="{{L}}">保存</button><button class="primary" bindtap="{{M}}">保存并继续</button></view></scroll-view>
<scroll-view scroll-y class="page"><view class="hero small"><text class="title">编辑货品</text><text class="sub">完善基础信息与价格</text></view><view wx:if="{{a}}" class="tip platform">平台推荐货品,建议谨慎修改核心字段</view><view wx:elif="{{b}}" class="tip custom">此货品源于我的提交,审核通过后已入库</view><view class="section"><view class="row"><text class="label">商品名称</text><input placeholder="必填" value="{{c}}" bindinput="{{d}}"/></view><view class="row"><text class="label">条形码</text><input class="input-long" placeholder="可扫码或输入" value="{{e}}" bindinput="{{f}}"/><button size="mini" class="picker-btn" bindtap="{{g}}">图片识码</button></view><view class="row"><input placeholder="品牌" value="{{h}}" bindinput="{{i}}"/></view><view class="row"><input placeholder="型号" value="{{j}}" bindinput="{{k}}"/></view><view class="row"><input placeholder="规格" value="{{l}}" bindinput="{{m}}"/></view><view class="row"><input placeholder="产地" value="{{n}}" bindinput="{{o}}"/></view><view class="row"><picker mode="selector" range="{{q}}" bindchange="{{r}}"><view class="picker">主单位:{{p}}</view></picker><picker mode="selector" range="{{t}}" bindchange="{{v}}"><view class="picker">类别:{{s}}</view></picker></view></view><view class="section"><view class="row"><text class="label">库存与安全库存</text></view><view class="row"><input type="number" placeholder="当前库存" value="{{w}}" bindinput="{{x}}"/><input type="number" placeholder="安全库存下限" value="{{y}}" bindinput="{{z}}"/><input type="number" placeholder="安全库存上限" value="{{A}}" bindinput="{{B}}"/></view></view><view class="section"><view class="row"><text class="label">价格(进价/零售/批发/大单)</text></view><view class="row prices"><input type="number" placeholder="进货价" value="{{C}}" bindinput="{{D}}"/><input type="number" placeholder="零售价" value="{{E}}" bindinput="{{F}}"/><input type="number" placeholder="批发价" value="{{G}}" bindinput="{{H}}"/><input type="number" placeholder="大单价" value="{{I}}" bindinput="{{J}}"/></view></view><view class="section"><text class="label">图片</text><image-uploader wx:if="{{L}}" u-i="4a3f460a-0" bind:__l="__l" bindupdateModelValue="{{K}}" u-p="{{L}}"/></view><view class="section"><text class="label">备注</text><block wx:if="{{r0}}"><textarea placeholder="可选" auto-height value="{{M}}" bindinput="{{N}}"/></block></view><view class="fixed" style="{{'bottom:' + Q}}"><button class="ghost" bindtap="{{O}}">保存</button><button class="primary" bindtap="{{P}}">保存并继续</button></view></scroll-view>

View File

@@ -26,71 +26,107 @@
/* 文章场景相关 */
.page {
background: #ffffff;
height: 100vh;
min-height: 100vh;
padding-bottom: 160rpx;
box-sizing: border-box;
}
.hero.small {
margin: 16rpx;
padding: 16rpx;
background: #ffffff;
border: 2rpx solid #e5e7eb;
border-radius: 16rpx;
margin: 22rpx 24rpx 12rpx;
padding: 0 4rpx 18rpx;
color: #111;
border-bottom: 2rpx solid rgba(94, 124, 174, 0.12);
}
.hero.small .title {
font-size: 32rpx;
font-size: 34rpx;
font-weight: 800;
color: #111;
}
.hero.small .sub {
margin-left: 12rpx;
display: block;
margin-top: 6rpx;
color: #444;
font-size: 24rpx;
}
.card {
background: #ffffff;
margin: 16rpx;
padding: 16rpx;
border-radius: 16rpx;
border: 2rpx solid #e5e7eb;
.section {
margin: 0 24rpx 28rpx;
padding-bottom: 6rpx;
border-bottom: 2rpx solid rgba(94, 124, 174, 0.1);
}
.section:last-of-type {
border-bottom: 0;
margin-bottom: 0;
}
.section .row:first-child .label {
font-weight: 700;
color: #111;
}
.row {
display: flex;
gap: 12rpx;
gap: 8rpx;
align-items: center;
margin-bottom: 12rpx;
margin-top: 18rpx;
}
.row .input-long {
flex: 1.2;
}
.row:first-child {
margin-top: 0;
}
.label {
width: 180rpx;
width: 150rpx;
color: #444;
font-size: 26rpx;
}
.row input {
flex: 1;
background: #f1f1f1;
border-radius: 12rpx;
padding: 14rpx;
background: #f7f9fc;
border-radius: 14rpx;
padding: 18rpx 20rpx;
color: #111;
border: 2rpx solid #e5e7eb;
border: 0;
box-shadow: inset 0 0 0 2rpx rgba(134, 155, 191, 0.06);
}
.picker-btn {
background: #ffffff;
border: 2rpx solid rgba(76, 141, 255, 0.45);
color: #4C8DFF;
padding: 0 24rpx;
border-radius: 999rpx;
font-size: 24rpx;
}
.picker {
padding: 10rpx 14rpx;
background: #f1f1f1;
border-radius: 12rpx;
padding: 16rpx 22rpx;
background: #f7f9fc;
border-radius: 14rpx;
color: #444;
margin-left: 8rpx;
border: 2rpx solid #e5e7eb;
border: 0;
box-shadow: inset 0 0 0 2rpx rgba(134, 155, 191, 0.06);
}
.prices input {
width: 30%;
}
.section textarea {
width: 100%;
min-height: 160rpx;
background: #f7f9fc;
border-radius: 14rpx;
padding: 20rpx 22rpx;
box-sizing: border-box;
color: #111;
border: 0;
box-shadow: inset 0 0 0 2rpx rgba(134, 155, 191, 0.06);
}
.fixed {
position: fixed;
left: 0;
right: 0;
bottom: 0;
bottom: env(safe-area-inset-bottom);
background: #ffffff;
padding: 12rpx 16rpx;
padding: 16rpx 16rpx calc(16rpx + constant(safe-area-inset-bottom)) 16rpx;
display: flex;
gap: 16rpx;
border-top: 2rpx solid #e5e7eb;
box-shadow: 0 -6rpx 18rpx rgba(24, 55, 105, 0.08);
z-index: 999;
}
.fixed .primary {
flex: 1;
@@ -107,4 +143,18 @@
border: 2rpx solid rgba(76, 141, 255, 0.45);
border-radius: 999rpx;
padding: 18rpx 0;
}
.tip {
margin: 0 30rpx 20rpx;
padding: 16rpx 20rpx;
border-radius: 16rpx;
font-size: 24rpx;
}
.tip.platform {
background: rgba(45, 140, 240, 0.12);
color: #2d8cf0;
}
.tip.custom {
background: rgba(103, 194, 58, 0.12);
color: #67c23a;
}

View File

@@ -98,6 +98,9 @@ const _sfc_main = {
openForm(id) {
const url = "/pages/product/form" + (id ? "?id=" + id : "");
common_vendor.index.navigateTo({ url });
},
goMySubmissions() {
common_vendor.index.navigateTo({ url: "/pages/product/submissions" });
}
}
};
@@ -107,39 +110,42 @@ function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
b: common_vendor.o(($event) => $options.switchTab("all")),
c: $data.tab === "category" ? 1 : "",
d: common_vendor.o(($event) => $options.switchTab("category")),
e: common_vendor.o((...args) => $options.reload && $options.reload(...args)),
f: $data.query.kw,
g: common_vendor.o(common_vendor.m(($event) => $data.query.kw = $event.detail.value, {
e: common_vendor.o((...args) => $options.goMySubmissions && $options.goMySubmissions(...args)),
f: common_vendor.o((...args) => $options.reload && $options.reload(...args)),
g: $data.query.kw,
h: common_vendor.o(common_vendor.m(($event) => $data.query.kw = $event.detail.value, {
trim: true
})),
h: $data.tab === "category"
i: $data.tab === "category"
}, $data.tab === "category" ? {
i: common_vendor.t($options.categoryLabel),
j: $options.categoryNames,
k: common_vendor.o((...args) => $options.onPickCategory && $options.onPickCategory(...args))
j: common_vendor.t($options.categoryLabel),
k: $options.categoryNames,
l: common_vendor.o((...args) => $options.onPickCategory && $options.onPickCategory(...args))
} : {}, {
l: common_vendor.o((...args) => $options.reload && $options.reload(...args)),
m: $data.items.length
m: common_vendor.o((...args) => $options.reload && $options.reload(...args)),
n: $data.items.length
}, $data.items.length ? {
n: common_vendor.f($data.items, (it, k0, i0) => {
o: common_vendor.f($data.items, (it, k0, i0) => {
return common_vendor.e({
a: it.cover
}, it.cover ? {
b: it.cover
} : {}, {
c: common_vendor.t(it.name),
d: common_vendor.t(it.brand || "-"),
e: common_vendor.t(it.model || ""),
f: common_vendor.t(it.spec || ""),
g: common_vendor.t(it.stock ?? 0),
h: common_vendor.t((it.retailPrice ?? it.price ?? 0).toFixed(2)),
i: it.id,
j: common_vendor.o(($event) => $options.openForm(it.id), it.id)
d: it.platformStatus === "platform"
}, it.platformStatus === "platform" ? {} : it.sourceSubmissionId ? {} : {}, {
e: it.sourceSubmissionId,
f: common_vendor.t(it.brand || "-"),
g: common_vendor.t(it.model || ""),
h: common_vendor.t(it.spec || ""),
i: common_vendor.t(it.stock ?? 0),
j: common_vendor.t((it.retailPrice ?? it.price ?? 0).toFixed(2)),
k: it.id,
l: common_vendor.o(($event) => $options.openForm(it.id), it.id)
});
})
} : {}, {
o: common_vendor.o((...args) => $options.loadMore && $options.loadMore(...args)),
p: common_vendor.o(($event) => $options.openForm())
p: common_vendor.o((...args) => $options.loadMore && $options.loadMore(...args))
});
}
const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render]]);

View File

@@ -1 +1 @@
<view class="page"><view class="tabs"><view class="{{['tab', a && 'active']}}" bindtap="{{b}}">全部</view><view class="{{['tab', c && 'active']}}" bindtap="{{d}}">按类别</view></view><view class="search"><input placeholder="输入名称/条码/规格查询" bindconfirm="{{e}}" value="{{f}}" bindinput="{{g}}"/><picker wx:if="{{h}}" mode="selector" range="{{j}}" bindchange="{{k}}"><view class="picker">{{i}}</view></picker><button size="mini" bindtap="{{l}}">查询</button></view><scroll-view scroll-y class="list" bindscrolltolower="{{o}}"><block wx:if="{{m}}"><view wx:for="{{n}}" wx:for-item="it" wx:key="i" class="item" bindtap="{{it.j}}"><image wx:if="{{it.a}}" src="{{it.b}}" class="thumb" mode="aspectFill"/><view class="content"><view class="name">{{it.c}}</view><view class="meta">{{it.d}} {{it.e}} {{it.f}}</view><view class="meta">库存:{{it.g}} <text class="price">零售价:¥{{it.h}}</text></view></view></view></block><view wx:else class="empty"><text>暂无数据,点击右上角“+”新增</text></view></scroll-view><view class="fab" bindtap="{{p}}"></view></view>
<view class="page"><view class="tabs"><view class="{{['tab', a && 'active']}}" bindtap="{{b}}">全部</view><view class="{{['tab', c && 'active']}}" bindtap="{{d}}">按类别</view><view class="tab extra" bindtap="{{e}}">我的提交</view></view><view class="search"><input placeholder="输入名称/条码/规格查询" bindconfirm="{{f}}" value="{{g}}" bindinput="{{h}}"/><picker wx:if="{{i}}" mode="selector" range="{{k}}" bindchange="{{l}}"><view class="picker">{{j}}</view></picker><button size="mini" bindtap="{{m}}">查询</button></view><scroll-view scroll-y class="list" bindscrolltolower="{{p}}"><block wx:if="{{n}}"><view wx:for="{{o}}" wx:for-item="it" wx:key="k" class="item" bindtap="{{it.l}}"><image wx:if="{{it.a}}" src="{{it.b}}" class="thumb" mode="aspectFill"/><view class="content"><view class="name"><text>{{it.c}}</text><text wx:if="{{it.d}}" class="tag-platform">平台推荐</text><text wx:elif="{{it.e}}" class="tag-custom">我的提交</text></view><view class="meta">{{it.f}} {{it.g}} {{it.h}}</view><view class="meta">库存:{{it.i}} <text class="price">零售价:¥{{it.j}}</text></view></view></view></block><view wx:else class="empty"><text>暂无数据,点击右上角“+”新增</text></view></scroll-view></view>

View File

@@ -43,6 +43,11 @@
color: #4C8DFF;
font-weight: 600;
}
.tab.extra {
flex: 0 0 180rpx;
color: #4C8DFF;
font-weight: 600;
}
.search {
display: flex;
gap: 12rpx;
@@ -86,6 +91,23 @@
color: #111;
margin-bottom: 6rpx;
font-weight: 600;
display: flex;
align-items: center;
gap: 12rpx;
}
.tag-platform {
font-size: 22rpx;
color: #fff;
background: #2d8cf0;
padding: 4rpx 10rpx;
border-radius: 8rpx;
}
.tag-custom {
font-size: 22rpx;
color: #fff;
background: #67c23a;
padding: 4rpx 10rpx;
border-radius: 8rpx;
}
.meta {
color: #444;

View File

@@ -0,0 +1,180 @@
"use strict";
const common_vendor = require("../../common/vendor.js");
const common_http = require("../../common/http.js");
const _sfc_main = {
data() {
return {
status: "",
items: [],
page: 1,
size: 20,
total: 0,
loading: false,
finished: false,
cacheUnitsLoaded: false,
cacheCategoriesLoaded: false
};
},
onShow() {
this.preloadDictionaries();
this.reload();
},
methods: {
async preloadDictionaries() {
try {
const [units, categories] = await Promise.all([
this.cacheUnitsLoaded ? Promise.resolve(null) : common_http.get("/api/product-units"),
this.cacheCategoriesLoaded ? Promise.resolve(null) : common_http.get("/api/product-categories")
]);
if (units) {
const list = Array.isArray(units == null ? void 0 : units.list) ? units.list : Array.isArray(units) ? units : [];
common_vendor.index.setStorageSync("CACHE_UNITS", list);
this.cacheUnitsLoaded = true;
}
if (categories) {
const list = Array.isArray(categories == null ? void 0 : categories.list) ? categories.list : Array.isArray(categories) ? categories : [];
common_vendor.index.setStorageSync("CACHE_CATEGORIES", list);
this.cacheCategoriesLoaded = true;
}
} catch (_) {
}
},
switchStatus(s) {
if (this.status === s)
return;
this.status = s;
this.reload();
},
async reload() {
this.page = 1;
this.items = [];
this.finished = false;
await this.loadMore();
},
async loadMore() {
if (this.loading || this.finished)
return;
this.loading = true;
try {
const params = { page: this.page, size: this.size };
if (this.status)
params.status = this.status;
const res = await common_http.get("/api/products/submissions", params);
const list = Array.isArray(res == null ? void 0 : res.list) ? res.list : [];
this.items = this.items.concat(list);
this.total = Number((res == null ? void 0 : res.total) || this.items.length);
if (list.length < this.size)
this.finished = true;
this.page += 1;
} catch (e) {
common_vendor.index.__f__("warn", "at pages/product/submissions.vue:113", "加载提交记录失败", e);
const msg = (e == null ? void 0 : e.message) || "加载失败";
common_vendor.index.showToast({ title: msg, icon: "none" });
} finally {
this.loading = false;
}
},
statusLabel(s) {
if (s === "approved")
return "已通过";
if (s === "rejected")
return "已驳回";
return "待审核";
},
statusClass(s) {
if (s === "approved")
return "approved";
if (s === "rejected")
return "rejected";
return "pending";
},
formatTime(value) {
if (!value)
return "-";
try {
const d = new Date(value);
if (!Number.isFinite(d.getTime()))
return value;
const y = d.getFullYear();
const m = String(d.getMonth() + 1).padStart(2, "0");
const day = String(d.getDate()).padStart(2, "0");
const hh = String(d.getHours()).padStart(2, "0");
const mm = String(d.getMinutes()).padStart(2, "0");
return `${y}-${m}-${day} ${hh}:${mm}`;
} catch (_) {
return value;
}
},
viewDetail(id) {
common_vendor.index.navigateTo({ url: `/pages/product/submission-detail?id=${id}` });
},
notifyPending() {
common_vendor.index.showToast({ title: "审核中,请耐心等待", icon: "none" });
},
resubmit(item) {
const payload = {
model: item.model,
name: item.name,
brand: item.brand,
spec: item.spec,
origin: item.origin,
unitId: item.unitId,
categoryId: item.categoryId,
remark: item.remark
};
const query = encodeURIComponent(JSON.stringify(payload));
common_vendor.index.navigateTo({ url: `/pages/product/submit?prefill=${query}` });
},
goSubmit() {
common_vendor.index.navigateTo({ url: "/pages/product/submit" });
}
}
};
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
return common_vendor.e({
a: $data.status === "" ? 1 : "",
b: common_vendor.o(($event) => $options.switchStatus("")),
c: $data.status === "pending" ? 1 : "",
d: common_vendor.o(($event) => $options.switchStatus("pending")),
e: $data.status === "approved" ? 1 : "",
f: common_vendor.o(($event) => $options.switchStatus("approved")),
g: $data.status === "rejected" ? 1 : "",
h: common_vendor.o(($event) => $options.switchStatus("rejected")),
i: $data.items.length
}, $data.items.length ? {
j: common_vendor.f($data.items, (item, k0, i0) => {
return common_vendor.e({
a: common_vendor.t(item.model || "-"),
b: common_vendor.t($options.statusLabel(item.status)),
c: common_vendor.n($options.statusClass(item.status)),
d: common_vendor.t(item.name || "未填写名称"),
e: common_vendor.t(item.brand || "-"),
f: common_vendor.t($options.formatTime(item.createdAt)),
g: item.reviewedAt
}, item.reviewedAt ? {
h: common_vendor.t($options.formatTime(item.reviewedAt))
} : {}, {
i: common_vendor.o(($event) => $options.viewDetail(item.id), item.id),
j: item.status === "pending"
}, item.status === "pending" ? {
k: common_vendor.o((...args) => $options.notifyPending && $options.notifyPending(...args), item.id)
} : item.status === "rejected" ? {
m: common_vendor.o(($event) => $options.resubmit(item), item.id)
} : {}, {
l: item.status === "rejected",
n: item.id
});
})
} : {
k: common_vendor.o((...args) => $options.goSubmit && $options.goSubmit(...args))
}, {
l: $data.loading
}, $data.loading ? {} : $data.finished && $data.items.length ? {} : {}, {
m: $data.finished && $data.items.length,
n: common_vendor.o((...args) => $options.loadMore && $options.loadMore(...args)),
o: common_vendor.o((...args) => $options.goSubmit && $options.goSubmit(...args))
});
}
const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render]]);
wx.createPage(MiniProgramPage);
//# sourceMappingURL=../../../.sourcemap/mp-weixin/pages/product/submissions.js.map

View File

@@ -0,0 +1,4 @@
{
"navigationBarTitleText": "我的提交",
"usingComponents": {}
}

View File

@@ -0,0 +1 @@
<view class="page"><view class="hero"><text class="title">我的配件提交</text><text class="desc">查看待审核、已通过、已驳回的记录</text></view><view class="tabs"><view class="{{['tab', a && 'active']}}" bindtap="{{b}}">全部</view><view class="{{['tab', c && 'active']}}" bindtap="{{d}}">待审核</view><view class="{{['tab', e && 'active']}}" bindtap="{{f}}">已通过</view><view class="{{['tab', g && 'active']}}" bindtap="{{h}}">已驳回</view></view><scroll-view scroll-y class="list" bindscrolltolower="{{n}}"><view wx:if="{{i}}" class="cards"><view wx:for="{{j}}" wx:for-item="item" wx:key="n" class="card"><view class="card-header"><text class="model">{{item.a}}</text><text class="{{['status', item.c]}}">{{item.b}}</text></view><view class="card-body"><text class="name">{{item.d}}</text><text class="brand">品牌:{{item.e}}</text><text class="time">提交:{{item.f}}</text><text wx:if="{{item.g}}" class="time">审核:{{item.h}}</text></view><view class="card-footer"><button size="mini" bindtap="{{item.i}}">详情</button><button wx:if="{{item.j}}" size="mini" type="primary" bindtap="{{item.k}}">等待审核</button><button wx:elif="{{item.l}}" size="mini" type="warn" bindtap="{{item.m}}">重新提交</button></view></view></view><view wx:else class="empty"><text>暂无提交记录,快去提交新的配件吧</text><button size="mini" class="primary" bindtap="{{k}}">立即提交</button></view><view wx:if="{{l}}" class="loading">加载中...</view><view wx:elif="{{m}}" class="finished">没有更多了</view></scroll-view><view class="fab" bindtap="{{o}}"></view></view>

View File

@@ -0,0 +1,163 @@
/**
* 这里是uni-app内置的常用样式变量
*
* uni-app 官方扩展插件及插件市场https://ext.dcloud.net.cn上很多三方插件均使用了这些样式变量
* 如果你是插件开发者建议你使用scss预处理并在插件代码中直接使用这些变量无需 import 这个文件方便用户通过搭积木的方式开发整体风格一致的App
*
*/
/**
* 如果你是App开发者插件使用者你可以通过修改这些变量来定制自己的插件主题实现自定义主题功能
*
* 如果你的项目同样使用了scss预处理你也可以直接在你的 scss 代码中使用如下变量,同时无需 import 这个文件
*/
/* 颜色变量 */
/* 行为相关颜色 */
/* 藏青系主色(高亮) */
/* 文字基本颜色 */
/* 背景颜色 */
/* 边框颜色 */
/* 尺寸变量 */
/* 文字尺寸 */
/* 图片尺寸 */
/* Border Radius */
/* 水平间距 */
/* 垂直间距 */
/* 透明度 */
/* 文章场景相关 */
.page {
display: flex;
flex-direction: column;
height: 100vh;
background: #f6f7fb;
padding-bottom: 140rpx;
}
.hero {
padding: 24rpx;
background: #fff;
box-shadow: 0 10rpx 30rpx rgba(0, 0, 0, 0.04);
}
.title {
font-size: 34rpx;
font-weight: 700;
color: #2d3a4a;
}
.desc {
font-size: 24rpx;
color: #7a8899;
margin-top: 8rpx;
}
.tabs {
display: flex;
background: #fff;
margin: 16rpx;
border-radius: 999rpx;
overflow: hidden;
box-shadow: inset 0 0 0 1rpx rgba(76, 141, 255, 0.1);
}
.tab {
flex: 1;
text-align: center;
padding: 20rpx 0;
font-size: 28rpx;
color: #7a8899;
}
.tab.active {
background: linear-gradient(135deg, #4c8dff, #6ab7ff);
color: #fff;
font-weight: 600;
}
.list {
flex: 1;
padding: 0 20rpx;
}
.cards {
display: flex;
flex-direction: column;
gap: 20rpx;
padding-bottom: 40rpx;
}
.card {
background: #fff;
border-radius: 18rpx;
padding: 22rpx;
box-shadow: 0 10rpx 30rpx rgba(0, 0, 0, 0.05);
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12rpx;
}
.model {
font-size: 30rpx;
font-weight: 700;
color: #2d3a4a;
}
.status {
font-size: 24rpx;
padding: 6rpx 18rpx;
border-radius: 999rpx;
}
.status.pending {
background: rgba(246, 190, 0, 0.15);
color: #c47f00;
}
.status.approved {
background: rgba(103, 194, 58, 0.15);
color: #409eff;
}
.status.rejected {
background: rgba(255, 87, 115, 0.18);
color: #f56c6c;
}
.card-body {
display: flex;
flex-direction: column;
gap: 6rpx;
color: #4f5969;
font-size: 26rpx;
}
.name {
font-weight: 600;
color: #2d3a4a;
}
.card-footer {
display: flex;
gap: 12rpx;
margin-top: 16rpx;
}
.empty {
height: 60vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: #8894a3;
gap: 20rpx;
}
.empty .primary {
background: #4c8dff;
color: #fff;
border-radius: 999rpx;
padding: 12rpx 30rpx;
}
.loading, .finished {
text-align: center;
padding: 20rpx 0;
color: #7a8899;
}
.fab {
position: fixed;
right: 30rpx;
bottom: 120rpx;
width: 100rpx;
height: 100rpx;
background: linear-gradient(135deg, #4c8dff, #6ab7ff);
color: #fff;
border-radius: 50rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 48rpx;
box-shadow: 0 20rpx 40rpx rgba(0, 0, 0, 0.2);
}

View File

@@ -0,0 +1,264 @@
"use strict";
const common_vendor = require("../../common/vendor.js");
const common_http = require("../../common/http.js");
const ImageUploader = () => "../../components/ImageUploader.js";
const _sfc_main = {
components: { ImageUploader },
data() {
return {
form: {
model: "",
brand: "",
barcode: "",
categoryId: "",
templateId: "",
parameters: {},
images: [],
remark: "",
safeMin: null,
safeMax: null
},
templates: [],
paramValues: {},
checking: false,
parameterText: "",
categories: [],
submitting: false,
paramPlaceholder: '可输入 JSON如 {"颜色":"黑","材质":"钢"}'
};
},
computed: {
categoryNames() {
return this.categories.map((c) => c.name);
},
templateNames() {
return this.templates.map((t) => t.name);
},
categoryLabel() {
const c = this.categories.find((x) => String(x.id) === String(this.form.categoryId));
return c ? c.name : "选择类别";
},
selectedTemplate() {
return this.templates.find((t) => String(t.id) === String(this.form.templateId));
},
templateLabel() {
const t = this.selectedTemplate;
return t ? `${t.name}` : "选择模板";
}
},
onLoad(options) {
this.bootstrap();
if (options && options.prefill) {
try {
const data = JSON.parse(decodeURIComponent(options.prefill));
Object.assign(this.form, {
model: data.model || "",
brand: data.brand || "",
barcode: data.barcode || "",
categoryId: data.categoryId || "",
remark: data.remark || ""
});
if (data.parameters && typeof data.parameters === "object") {
this.parameterText = JSON.stringify(data.parameters, null, 2);
}
} catch (_) {
}
}
},
methods: {
async bootstrap() {
await Promise.all([this.fetchCategories()]);
await this.fetchTemplates();
},
async fetchCategories() {
try {
const res = await common_http.get("/api/product-categories");
this.categories = Array.isArray(res == null ? void 0 : res.list) ? res.list : Array.isArray(res) ? res : [];
} catch (_) {
this.categories = [];
}
},
async fetchTemplates() {
try {
const res = await common_http.get("/api/product-templates", this.form.categoryId ? { categoryId: this.form.categoryId } : {});
const list = Array.isArray(res == null ? void 0 : res.list) ? res.list : Array.isArray(res) ? res : [];
this.templates = list;
} catch (_) {
this.templates = [];
}
},
onPickCategory(e) {
const idx = Number(e.detail.value);
const c = this.categories[idx];
this.form.categoryId = c ? c.id : "";
this.fetchTemplates();
},
onPickTemplate(e) {
const idx = Number(e.detail.value);
const t = this.templates[idx];
this.form.templateId = t ? t.id : "";
this.paramValues = {};
},
onPickEnum(p, e) {
const idx = Number(e.detail.value);
const arr = p.enumOptions || [];
this.paramValues[p.fieldKey] = arr[idx];
},
async scanBarcode() {
var _a;
try {
const chooseRes = await common_vendor.index.chooseImage({ count: 1, sourceType: ["camera", "album"], sizeType: ["compressed"] });
let filePath = chooseRes.tempFilePaths[0];
try {
const comp = await common_vendor.index.compressImage({ src: filePath, quality: 80 });
filePath = comp.tempFilePath || filePath;
} catch (_) {
}
const data = await common_http.upload("/api/barcode/scan", filePath, {}, "file");
const barcode = (data == null ? void 0 : data.barcode) || ((_a = data == null ? void 0 : data.data) == null ? void 0 : _a.barcode);
if (barcode) {
this.form.barcode = barcode;
common_vendor.index.showToast({ title: "识别成功", icon: "success" });
} else {
common_vendor.index.showToast({ title: "未识别到条码", icon: "none" });
}
} catch (e) {
const msg = (e == null ? void 0 : e.message) || "识码失败";
common_vendor.index.showToast({ title: msg, icon: "none" });
}
},
async checkModel() {
if (!this.form.model)
return common_vendor.index.showToast({ title: "请填写型号", icon: "none" });
try {
this.checking = true;
const res = await common_http.post("/api/products/submissions/check-model", { templateId: this.form.templateId, model: this.form.model });
if (res && res.available) {
common_vendor.index.showToast({ title: "可用,无重复", icon: "success" });
} else {
common_vendor.index.showToast({ title: "已存在相同型号提交", icon: "none" });
}
} catch (e) {
const msg = (e == null ? void 0 : e.message) || "校验失败";
common_vendor.index.showToast({ title: msg, icon: "none" });
} finally {
this.checking = false;
}
},
async submit() {
if (this.submitting)
return;
if (!this.form.model) {
return common_vendor.index.showToast({ title: "请填写型号", icon: "none" });
}
let paramsObj = null;
if (this.parameterText) {
try {
paramsObj = JSON.parse(this.parameterText);
} catch (e) {
return common_vendor.index.showToast({ title: "参数 JSON 不合法", icon: "none" });
}
}
if (this.form.safeMin != null && this.form.safeMax != null && Number(this.form.safeMin) > Number(this.form.safeMax)) {
return common_vendor.index.showToast({ title: "安全库存区间不合法", icon: "none" });
}
let paramsForSubmit = paramsObj;
if (this.selectedTemplate) {
for (const p of this.selectedTemplate.params || []) {
if (p.required && (this.paramValues[p.fieldKey] === void 0 || this.paramValues[p.fieldKey] === null || this.paramValues[p.fieldKey] === "")) {
return common_vendor.index.showToast({ title: `请填写 ${p.fieldLabel}`, icon: "none" });
}
}
const shaped = {};
for (const p of this.selectedTemplate.params || []) {
let v = this.paramValues[p.fieldKey];
if (p.type === "number" && v !== void 0 && v !== null && v !== "")
v = Number(v);
if (p.type === "boolean")
v = !!v;
shaped[p.fieldKey] = v;
}
paramsForSubmit = shaped;
}
const payload = {
model: this.form.model,
brand: this.form.brand,
barcode: this.form.barcode,
categoryId: this.form.categoryId || null,
templateId: this.form.templateId || null,
parameters: paramsForSubmit,
images: this.form.images,
remark: this.form.remark,
safeMin: this.form.safeMin,
safeMax: this.form.safeMax
};
this.submitting = true;
try {
await common_http.post("/api/products/submissions", payload);
common_vendor.index.showToast({ title: "提交成功", icon: "success" });
setTimeout(() => {
common_vendor.index.redirectTo({ url: "/pages/product/submissions" });
}, 400);
} catch (e) {
const msg = (e == null ? void 0 : e.message) || "提交失败";
common_vendor.index.showToast({ title: msg, icon: "none" });
} finally {
this.submitting = false;
}
}
}
};
if (!Array) {
const _component_ImageUploader = common_vendor.resolveComponent("ImageUploader");
_component_ImageUploader();
}
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
return {
a: $data.form.model,
b: common_vendor.o(common_vendor.m(($event) => $data.form.model = $event.detail.value, {
trim: true
})),
c: $data.form.brand,
d: common_vendor.o(common_vendor.m(($event) => $data.form.brand = $event.detail.value, {
trim: true
})),
e: $data.form.barcode,
f: common_vendor.o(common_vendor.m(($event) => $data.form.barcode = $event.detail.value, {
trim: true
})),
g: common_vendor.o((...args) => $options.scanBarcode && $options.scanBarcode(...args)),
h: common_vendor.t($options.categoryLabel),
i: $options.categoryNames,
j: common_vendor.o((...args) => $options.onPickCategory && $options.onPickCategory(...args)),
k: common_vendor.t($options.templateLabel),
l: $options.templateNames,
m: common_vendor.o((...args) => $options.onPickTemplate && $options.onPickTemplate(...args)),
n: common_vendor.o(($event) => $data.form.images = $event),
o: common_vendor.p({
max: 9,
formData: {
ownerType: "submission"
},
modelValue: $data.form.images
}),
p: $data.form.remark,
q: common_vendor.o(common_vendor.m(($event) => $data.form.remark = $event.detail.value, {
trim: true
})),
r: $data.form.safeMin,
s: common_vendor.o(common_vendor.m(($event) => $data.form.safeMin = $event.detail.value, {
number: true
})),
t: $data.form.safeMax,
v: common_vendor.o(common_vendor.m(($event) => $data.form.safeMax = $event.detail.value, {
number: true
})),
w: $data.submitting,
x: common_vendor.o((...args) => $options.submit && $options.submit(...args)),
y: $data.checking,
z: common_vendor.o((...args) => $options.checkModel && $options.checkModel(...args))
};
}
const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render]]);
wx.createPage(MiniProgramPage);
//# sourceMappingURL=../../../.sourcemap/mp-weixin/pages/product/submit.js.map

View File

@@ -0,0 +1,7 @@
{
"navigationBarTitleText": "提交配件",
"navigationBarBackgroundColor": "#ffffff",
"usingComponents": {
"image-uploader": "../../components/ImageUploader"
}
}

View File

@@ -0,0 +1 @@
<scroll-view scroll-y class="page"><view class="hero"><text class="title">提交配件</text><text class="desc">填写型号、名称、参数与图片,提交后进入待审核状态</text></view><view class="section"><view class="row required"><text class="label">型号</text><input placeholder="请输入型号(必填)" value="{{a}}" bindinput="{{b}}"/></view><view class="row"><text class="label">品牌</text><input placeholder="品牌/厂商" value="{{c}}" bindinput="{{d}}"/></view><view class="row"><text class="label">条码</text><input placeholder="可选,建议扫码录入" value="{{e}}" bindinput="{{f}}"/><button size="mini" class="picker-btn" bindtap="{{g}}">识码</button></view></view><view class="section"><view class="row"><text class="label">类别</text><picker mode="selector" range="{{i}}" bindchange="{{j}}"><view class="picker">{{h}}</view></picker></view></view><view class="section"><view class="row"><text class="label">模板</text></view><view class="row"><picker mode="selector" range="{{l}}" bindchange="{{m}}"><view class="picker">{{k}}</view></picker></view></view><view class="section"><view class="row"><text class="label">图片</text></view><image-uploader wx:if="{{o}}" u-i="7f3a3bde-0" bind:__l="__l" bindupdateModelValue="{{n}}" u-p="{{o}}"/></view><view class="section"><view class="row"><text class="label">备注</text></view><block wx:if="{{r0}}"><textarea class="textarea" placeholder="选填:补充说明" value="{{p}}" bindinput="{{q}}"/></block></view><view class="section"><view class="row"><text class="label">安全库存</text></view><view class="row triple"><input type="number" placeholder="下限" value="{{r}}" bindinput="{{s}}"/><input type="number" placeholder="上限" value="{{t}}" bindinput="{{v}}"/></view></view><view class="fixed"><button class="primary" loading="{{w}}" bindtap="{{x}}">提交审核</button><button class="primary" style="margin-top:16rpx;background:#7aa9ff" loading="{{y}}" bindtap="{{z}}">查重</button></view></scroll-view>

View File

@@ -0,0 +1,125 @@
/**
* 这里是uni-app内置的常用样式变量
*
* uni-app 官方扩展插件及插件市场https://ext.dcloud.net.cn上很多三方插件均使用了这些样式变量
* 如果你是插件开发者建议你使用scss预处理并在插件代码中直接使用这些变量无需 import 这个文件方便用户通过搭积木的方式开发整体风格一致的App
*
*/
/**
* 如果你是App开发者插件使用者你可以通过修改这些变量来定制自己的插件主题实现自定义主题功能
*
* 如果你的项目同样使用了scss预处理你也可以直接在你的 scss 代码中使用如下变量,同时无需 import 这个文件
*/
/* 颜色变量 */
/* 行为相关颜色 */
/* 藏青系主色(高亮) */
/* 文字基本颜色 */
/* 背景颜色 */
/* 边框颜色 */
/* 尺寸变量 */
/* 文字尺寸 */
/* 图片尺寸 */
/* Border Radius */
/* 水平间距 */
/* 垂直间距 */
/* 透明度 */
/* 文章场景相关 */
.page {
padding: 24rpx 24rpx 120rpx;
background: #f6f7fb;
}
.hero {
padding: 24rpx;
background: linear-gradient(135deg, #4c8dff, #6ab7ff);
border-radius: 20rpx;
color: #fff;
margin-bottom: 24rpx;
}
.title {
font-size: 36rpx;
font-weight: 700;
}
.desc {
font-size: 26rpx;
margin-top: 8rpx;
opacity: 0.9;
}
.section {
background: #fff;
border-radius: 16rpx;
padding: 20rpx 22rpx;
margin-bottom: 24rpx;
box-shadow: 0 10rpx 30rpx rgba(0, 0, 0, 0.04);
}
.row {
display: flex;
align-items: center;
gap: 16rpx;
padding: 16rpx 0;
border-bottom: 1rpx solid #f1f2f5;
}
.row:last-child {
border-bottom: none;
}
.row.required .label::after {
content: "*";
color: #ff5b5b;
margin-left: 6rpx;
}
.label {
width: 130rpx;
font-size: 28rpx;
color: #2d3a4a;
}
input {
flex: 1;
background: #f8f9fb;
border-radius: 12rpx;
padding: 16rpx 18rpx;
font-size: 28rpx;
color: #222;
}
.textarea {
width: 100%;
min-height: 160rpx;
background: #f8f9fb;
border-radius: 12rpx;
padding: 18rpx;
font-size: 28rpx;
color: #222;
}
.picker {
flex: 1;
background: #f8f9fb;
border-radius: 12rpx;
padding: 18rpx;
font-size: 28rpx;
color: #222;
}
.picker-btn {
background: #4c8dff;
color: #fff;
border-radius: 999rpx;
padding: 10rpx 22rpx;
}
.triple input {
flex: 1;
}
.fixed {
position: fixed;
left: 0;
right: 0;
bottom: 0;
padding: 20rpx 24rpx 40rpx;
background: rgba(255, 255, 255, 0.96);
box-shadow: 0 -6rpx 20rpx rgba(0, 0, 0, 0.08);
}
.primary {
width: 100%;
height: 88rpx;
border-radius: 999rpx;
background: #4c8dff;
color: #fff;
font-size: 32rpx;
font-weight: 600;
}

View File

@@ -1,6 +1,6 @@
"use strict";
const common_vendor = require("../../common/vendor.js");
const common_http = require("../../common/http.js");
const common_vendor = require("../../common/vendor.js");
function formatDate(d) {
const y = d.getFullYear();
const m = String(d.getMonth() + 1).padStart(2, "0");
@@ -14,39 +14,62 @@ const _sfc_main = {
return {
startDate: formatDate(start),
endDate: formatDate(now),
mode: "sale",
dim: "customer",
rows: [],
total: { sales: 0, cost: 0, profit: 0 }
summary: { salesAmount: 0, costAmount: 0, profit: 0, profitRate: 0, itemCount: 0 },
loading: false,
error: ""
};
},
onLoad(query) {
try {
const m = query && query.mode;
const d = query && query.dim;
if (m)
this.mode = m;
if (d)
if (d === "product" || d === "customer")
this.dim = d;
} catch (e) {
}
this.refresh();
},
computed: {
profitRate() {
const { sales, profit } = this.total;
if (!sales)
return "0.00%";
return (profit / sales * 100).toFixed(2) + "%";
profitRateText() {
var _a;
const rate = Number(((_a = this.summary) == null ? void 0 : _a.profitRate) || 0);
return rate.toFixed(2) + "%";
},
summaryItems() {
if (!this.rows.length)
return [];
return [
{ label: "销售额", value: `${this.fmt(this.summary.salesAmount)}` },
{ label: "成本", value: `${this.fmt(this.summary.costAmount)}` },
{ label: "利润", value: `${this.fmt(this.summary.profit)}` },
{ label: "利润率", value: this.profitRateText }
];
}
},
methods: {
fmt(n) {
return Number(n || 0).toFixed(2);
},
setMode(m) {
this.mode = m;
this.dim = m === "sale" ? "customer" : m === "purchase" ? "supplier" : m === "inventory" ? "qty" : "ar";
showProductSpec(row) {
return this.dim === "product" && row && row.spec;
},
rowMetrics(row) {
if (!row)
return [];
return [
{ label: "销售额", value: `${this.fmt(row.salesAmount)}` },
{ label: "成本", value: `${this.fmt(row.costAmount)}` },
{ label: "利润", value: `${this.fmt(row.profit)}` },
{ label: "利润率", value: `${Number(row.profitRate || 0).toFixed(2)}%` }
];
},
setDimension(d) {
if (d !== "customer" && d !== "product")
return;
if (this.dim === d)
return;
this.dim = d;
this.refresh();
},
onStartChange(e) {
@@ -58,279 +81,85 @@ const _sfc_main = {
this.refresh();
},
async refresh() {
if (this.mode === "sale") {
if (this.dim === "customer")
return this.loadByCustomer();
if (this.dim === "product")
return this.loadByProduct();
}
if (this.mode === "purchase") {
if (this.dim === "supplier")
return this.loadPurchaseBySupplier();
if (this.dim === "product")
return this.loadPurchaseByProduct();
}
if (this.mode === "inventory") {
if (this.dim === "qty")
return this.loadInventoryByQty();
if (this.dim === "amount")
return this.loadInventoryByAmount();
}
if (this.mode === "arap") {
if (this.dim === "ar")
return this.loadAR();
if (this.dim === "ap")
return this.loadAP();
}
},
async loadByCustomer() {
var _a, _b, _c, _d, _e;
this.loading = true;
this.error = "";
try {
const listResp = await common_http.get("/api/orders", { biz: "sale", type: "out", startDate: this.startDate, endDate: this.endDate, page: 1, size: 200 });
const list = listResp && (listResp.list || listResp) || [];
const map = /* @__PURE__ */ new Map();
let totalSales = 0;
for (const it of list) {
const name = it.customerName || "未知客户";
const amount = Number(it.amount || 0);
totalSales += amount;
if (!map.has(name))
map.set(name, { name, sales: 0, cost: 0, profit: 0 });
const row = map.get(name);
row.sales += amount;
}
const rows = Array.from(map.values()).map((r) => ({ ...r, profit: r.sales - r.cost }));
const total = { sales: totalSales, cost: 0, profit: totalSales };
this.rows = rows;
this.total = total;
const resp = await common_http.get("/api/report/sales", {
dimension: this.dim,
startDate: this.startDate,
endDate: this.endDate
});
const items = Array.isArray(resp == null ? void 0 : resp.items) ? resp.items : [];
this.rows = items.map((it) => ({
name: (it == null ? void 0 : it.name) || (this.dim === "product" ? "未命名商品" : "未指定客户"),
spec: (it == null ? void 0 : it.spec) || "",
salesAmount: Number((it == null ? void 0 : it.salesAmount) || 0),
costAmount: Number((it == null ? void 0 : it.costAmount) || 0),
profit: Number((it == null ? void 0 : it.profit) || 0),
profitRate: Number((it == null ? void 0 : it.profitRate) || 0)
}));
this.summary = {
salesAmount: Number(((_a = resp == null ? void 0 : resp.summary) == null ? void 0 : _a.salesAmount) || 0),
costAmount: Number(((_b = resp == null ? void 0 : resp.summary) == null ? void 0 : _b.costAmount) || 0),
profit: Number(((_c = resp == null ? void 0 : resp.summary) == null ? void 0 : _c.profit) || 0),
profitRate: Number(((_d = resp == null ? void 0 : resp.summary) == null ? void 0 : _d.profitRate) || 0),
itemCount: Number(((_e = resp == null ? void 0 : resp.summary) == null ? void 0 : _e.itemCount) || this.rows.length)
};
} catch (e) {
common_vendor.index.showToast({ title: "加载失败", icon: "none" });
}
},
async loadByProduct() {
try {
const listResp = await common_http.get("/api/orders", { biz: "sale", type: "out", startDate: this.startDate, endDate: this.endDate, page: 1, size: 200 });
const list = listResp && (listResp.list || listResp) || [];
const agg = /* @__PURE__ */ new Map();
for (const it of list) {
try {
const d = await common_http.get(`/api/orders/${it.id}`);
const items = d && d.items || [];
for (const m of items) {
const key = String(m.productId || m.name);
if (!agg.has(key))
agg.set(key, { name: m.name || "#" + key, sales: 0, cost: 0, profit: 0 });
const row = agg.get(key);
const sales = Number(m.amount || 0);
row.sales += sales;
}
} catch (_) {
}
}
const rows = Array.from(agg.values()).map((r) => ({ ...r, profit: r.sales - r.cost }));
const totalSales = rows.reduce((s, r) => s + r.sales, 0);
this.rows = rows;
this.total = { sales: totalSales, cost: 0, profit: totalSales };
} catch (e) {
common_vendor.index.showToast({ title: "加载失败", icon: "none" });
}
},
async loadPurchaseBySupplier() {
try {
const listResp = await common_http.get("/api/purchase-orders", { startDate: this.startDate, endDate: this.endDate, page: 1, size: 200 });
const list = listResp && (listResp.list || listResp) || [];
const map = /* @__PURE__ */ new Map();
let total = 0;
for (const it of list) {
const name = it.supplierName || "未知供应商";
const amount = Number(it.amount || 0);
total += amount;
if (!map.has(name))
map.set(name, { name, sales: 0, cost: 0, profit: 0 });
const row = map.get(name);
row.sales += amount;
}
this.rows = Array.from(map.values());
this.total = { sales: total, cost: 0, profit: 0 };
} catch (e) {
common_vendor.index.showToast({ title: "加载失败", icon: "none" });
}
},
async loadPurchaseByProduct() {
try {
const listResp = await common_http.get("/api/purchase-orders", { startDate: this.startDate, endDate: this.endDate, page: 1, size: 200 });
const list = listResp && (listResp.list || listResp) || [];
const agg = /* @__PURE__ */ new Map();
for (const it of list) {
try {
const d = await common_http.get(`/api/purchase-orders/${it.id}`);
for (const m of (d == null ? void 0 : d.items) || []) {
const key = String(m.productId || m.name);
if (!agg.has(key))
agg.set(key, { name: m.name || "#" + key, sales: 0, cost: 0, profit: 0 });
const row = agg.get(key);
row.sales += Number(m.amount || 0);
}
} catch (_) {
}
}
const rows = Array.from(agg.values());
const total = rows.reduce((s, r) => s + r.sales, 0);
this.rows = rows;
this.total = { sales: total, cost: 0, profit: 0 };
} catch (e) {
common_vendor.index.showToast({ title: "加载失败", icon: "none" });
}
},
async loadInventoryByQty() {
try {
const resp = await common_http.get("/api/inventories/logs", { startDate: this.startDate, endDate: this.endDate, page: 1, size: 200 });
const list = resp && (resp.list || resp) || [];
const map = /* @__PURE__ */ new Map();
let totalQty = 0;
for (const it of list) {
const key = it.productId || "未知";
if (!map.has(key))
map.set(key, { name: String(key), sales: 0, cost: 0, profit: 0 });
const row = map.get(key);
const q = Number(it.qtyDelta || 0);
row.sales += q;
totalQty += q;
}
this.rows = Array.from(map.values());
this.total = { sales: totalQty, cost: 0, profit: 0 };
} catch (e) {
common_vendor.index.showToast({ title: "加载失败", icon: "none" });
}
},
async loadInventoryByAmount() {
try {
const resp = await common_http.get("/api/inventories/logs", { startDate: this.startDate, endDate: this.endDate, page: 1, size: 200 });
const list = resp && (resp.list || resp) || [];
const map = /* @__PURE__ */ new Map();
let totalAmt = 0;
for (const it of list) {
const key = it.productId || "未知";
if (!map.has(key))
map.set(key, { name: String(key), sales: 0, cost: 0, profit: 0 });
const row = map.get(key);
const a = Number(it.amount || it.amountDelta || 0);
row.sales += a;
totalAmt += a;
}
this.rows = Array.from(map.values());
this.total = { sales: totalAmt, cost: 0, profit: 0 };
} catch (e) {
common_vendor.index.showToast({ title: "加载失败", icon: "none" });
}
},
async loadAR() {
try {
const res = await common_http.get("/api/customers", { page: 1, size: 100, debtOnly: false });
const list = Array.isArray(res == null ? void 0 : res.list) ? res.list : Array.isArray(res) ? res : [];
const rows = list.map((c) => ({ name: c.name, sales: Number(c.receivable || 0), cost: 0, profit: 0 }));
const total = rows.reduce((s, r) => s + r.sales, 0);
this.rows = rows;
this.total = { sales: total, cost: 0, profit: 0 };
} catch (e) {
common_vendor.index.showToast({ title: "加载失败", icon: "none" });
}
},
async loadAP() {
try {
const res = await common_http.get("/api/suppliers", { page: 1, size: 100 });
const list = Array.isArray(res == null ? void 0 : res.list) ? res.list : Array.isArray(res) ? res : [];
const rows = list.map((s) => ({ name: s.name, sales: Number(s.apPayable || 0), cost: 0, profit: 0 }));
const total = rows.reduce((s, r) => s + r.sales, 0);
this.rows = rows;
this.total = { sales: total, cost: 0, profit: 0 };
} catch (e) {
common_vendor.index.showToast({ title: "加载失败", icon: "none" });
this.error = e && e.message || "报表加载失败";
} finally {
this.loading = false;
}
}
}
};
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
return common_vendor.e({
a: $data.mode === "sale" ? 1 : "",
b: common_vendor.o(($event) => $options.setMode("sale")),
c: $data.mode === "purchase" ? 1 : "",
d: common_vendor.o(($event) => $options.setMode("purchase")),
e: $data.mode === "inventory" ? 1 : "",
f: common_vendor.o(($event) => $options.setMode("inventory")),
g: $data.mode === "arap" ? 1 : "",
h: common_vendor.o(($event) => $options.setMode("arap")),
i: common_vendor.t($data.startDate),
j: $data.startDate,
k: common_vendor.o((...args) => $options.onStartChange && $options.onStartChange(...args)),
l: common_vendor.t($data.endDate),
m: $data.endDate,
n: common_vendor.o((...args) => $options.onEndChange && $options.onEndChange(...args)),
o: $data.mode === "sale"
}, $data.mode === "sale" ? {
p: $data.dim === "customer" ? 1 : "",
q: common_vendor.o(($event) => {
$data.dim = "customer";
$options.refresh();
}),
r: $data.dim === "product" ? 1 : "",
s: common_vendor.o(($event) => {
$data.dim = "product";
$options.refresh();
})
} : $data.mode === "purchase" ? {
v: $data.dim === "supplier" ? 1 : "",
w: common_vendor.o(($event) => {
$data.dim = "supplier";
$options.refresh();
}),
x: $data.dim === "product" ? 1 : "",
y: common_vendor.o(($event) => {
$data.dim = "product";
$options.refresh();
})
} : $data.mode === "inventory" ? {
A: $data.dim === "qty" ? 1 : "",
B: common_vendor.o(($event) => {
$data.dim = "qty";
$options.refresh();
}),
C: $data.dim === "amount" ? 1 : "",
D: common_vendor.o(($event) => {
$data.dim = "amount";
$options.refresh();
})
} : $data.mode === "arap" ? {
F: $data.dim === "ar" ? 1 : "",
G: common_vendor.o(($event) => {
$data.dim = "ar";
$options.refresh();
}),
H: $data.dim === "ap" ? 1 : "",
I: common_vendor.o(($event) => {
$data.dim = "ap";
$options.refresh();
a: common_vendor.t($data.startDate),
b: $data.startDate,
c: common_vendor.o((...args) => $options.onStartChange && $options.onStartChange(...args)),
d: common_vendor.t($data.endDate),
e: $data.endDate,
f: common_vendor.o((...args) => $options.onEndChange && $options.onEndChange(...args)),
g: $data.dim === "customer" ? 1 : "",
h: common_vendor.o(($event) => $options.setDimension("customer")),
i: $data.dim === "product" ? 1 : "",
j: common_vendor.o(($event) => $options.setDimension("product")),
k: $options.summaryItems.length
}, $options.summaryItems.length ? {
l: common_vendor.f($options.summaryItems, (item, ix, i0) => {
return {
a: common_vendor.t(item.label),
b: common_vendor.t(item.value),
c: ix
};
})
} : {}, {
t: $data.mode === "purchase",
z: $data.mode === "inventory",
E: $data.mode === "arap",
J: common_vendor.t($options.fmt($data.total.sales)),
K: common_vendor.t($options.fmt($data.total.cost)),
L: common_vendor.t($options.fmt($data.total.profit)),
M: common_vendor.t($options.profitRate),
N: common_vendor.f($data.rows, (row, idx, i0) => {
m: $data.loading
}, $data.loading ? {} : $data.error ? {
o: common_vendor.t($data.error)
} : !$data.rows.length ? {} : {
q: common_vendor.f($data.rows, (row, idx, i0) => {
return common_vendor.e({
a: row.avatar
}, row.avatar ? {
b: row.avatar
a: common_vendor.t(row.name),
b: $options.showProductSpec(row)
}, $options.showProductSpec(row) ? {
c: common_vendor.t(row.spec)
} : {}, {
c: common_vendor.t(row.name),
d: common_vendor.t($options.fmt(row.sales)),
e: common_vendor.t($options.fmt(row.cost)),
f: common_vendor.t($options.fmt(row.profit)),
g: idx
d: common_vendor.f($options.rowMetrics(row), (metric, mIdx, i1) => {
return {
a: common_vendor.t(metric.label),
b: common_vendor.t(metric.value),
c: mIdx
};
}),
e: idx
});
})
}, {
n: $data.error,
p: !$data.rows.length
});
}
const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render]]);

View File

@@ -1 +1 @@
<view class="report"><view class="modes"><view class="{{['mode-tab', a && 'active']}}" bindtap="{{b}}">销售统计</view><view class="{{['mode-tab', c && 'active']}}" bindtap="{{d}}">进货统计</view><view class="{{['mode-tab', e && 'active']}}" bindtap="{{f}}">库存统计</view><view class="{{['mode-tab', g && 'active']}}" bindtap="{{h}}">应收/应付对账</view></view><view class="toolbar"><picker mode="date" value="{{j}}" bindchange="{{k}}"><view class="date">{{i}}</view></picker><text style="margin:0 8rpx">—</text><picker mode="date" value="{{m}}" bindchange="{{n}}"><view class="date">{{l}}</view></picker></view><view wx:if="{{o}}" class="tabs"><view class="{{['tab', p && 'active']}}" bindtap="{{q}}">按客户</view><view class="{{['tab', r && 'active']}}" bindtap="{{s}}">按货品</view></view><view wx:elif="{{t}}" class="tabs"><view class="{{['tab', v && 'active']}}" bindtap="{{w}}">按供应商</view><view class="{{['tab', x && 'active']}}" bindtap="{{y}}">按货品</view></view><view wx:elif="{{z}}" class="tabs"><view class="{{['tab', A && 'active']}}" bindtap="{{B}}">按数量</view><view class="{{['tab', C && 'active']}}" bindtap="{{D}}">按金额</view></view><view wx:elif="{{E}}" class="tabs"><view class="{{['tab', F && 'active']}}" bindtap="{{G}}">应收对账</view><view class="{{['tab', H && 'active']}}" bindtap="{{I}}">应付对账</view></view><view class="summary"><view class="item"><text class="label">销售额</text><text class="value">¥ {{J}}</text></view><view class="item"><text class="label">成本</text><text class="value">¥ {{K}}</text></view><view class="item"><text class="label">利润</text><text class="value">¥ {{L}}</text></view><view class="item"><text class="label">利润率</text><text class="value">{{M}}</text></view></view><view wx:for="{{N}}" wx:for-item="row" wx:key="g" class="card"><view class="row-head"><image wx:if="{{row.a}}" class="thumb" src="{{row.b}}"/><view class="title">{{row.c}}</view></view><view class="row-body"><text>销售额:¥ {{row.d}}</text><text style="margin-left:18rpx">成本:¥ {{row.e}}</text><text style="margin-left:18rpx">利润:¥ {{row.f}}</text></view></view></view>
<view class="report"><view class="header">销售报表</view><view class="toolbar"><picker mode="date" value="{{b}}" bindchange="{{c}}"><view class="date">{{a}}</view></picker><text style="margin:0 8rpx">—</text><picker mode="date" value="{{e}}" bindchange="{{f}}"><view class="date">{{d}}</view></picker></view><view class="tabs"><view class="{{['tab', g && 'active']}}" bindtap="{{h}}">按客户</view><view class="{{['tab', i && 'active']}}" bindtap="{{j}}">按货品</view></view><view wx:if="{{k}}" class="summary"><view wx:for="{{l}}" wx:for-item="item" wx:key="c" class="summary-item"><text class="label">{{item.a}}</text><text class="value">{{item.b}}</text></view></view><view wx:if="{{m}}" class="loading">加载中...</view><view wx:elif="{{n}}" class="empty">{{o}}</view><view wx:elif="{{p}}" class="empty">暂无统计数据</view><view wx:else><view wx:for="{{q}}" wx:for-item="row" wx:key="e" class="card"><view class="row-head"><view class="row-title"><view class="title">{{row.a}}</view><view wx:if="{{row.b}}" class="subtitle">{{row.c}}</view></view></view><view class="row-body"><view wx:for="{{row.d}}" wx:for-item="metric" wx:key="c" class="metric"><text class="metric-label">{{metric.a}}</text><text class="metric-value">{{metric.b}}</text></view></view></view></view></view>

View File

@@ -25,101 +25,133 @@
/* 透明度 */
/* 文章场景相关 */
.report {
padding: 20rpx;
}
.modes {
padding: 24rpx 20rpx 36rpx;
display: flex;
gap: 12rpx;
margin-bottom: 14rpx;
flex-direction: column;
gap: 18rpx;
}
.mode-tab {
flex: 1;
text-align: center;
padding: 16rpx 0;
border-radius: 999rpx;
background: #f1f1f1;
color: #444;
border: 1rpx solid #e5e7eb;
}
.mode-tab.active {
background: #4C8DFF;
color: #fff;
border-color: #4C8DFF;
.header {
font-size: 34rpx;
font-weight: 700;
color: #1f2a44;
padding-left: 8rpx;
}
.toolbar {
display: flex;
align-items: center;
gap: 8rpx;
background: #ffffff;
padding: 14rpx 16rpx;
border-radius: 12rpx;
justify-content: center;
gap: 12rpx;
background: #f7f9fc;
border-radius: 16rpx;
padding: 18rpx;
}
.date {
padding: 10rpx 16rpx;
border: 1rpx solid #e5e7eb;
border-radius: 8rpx;
color: #111;
min-width: 200rpx;
padding: 12rpx 18rpx;
border-radius: 12rpx;
background: #fff;
border: 1rpx solid rgba(91, 107, 139, 0.16);
text-align: center;
color: #32445b;
}
.tabs {
display: flex;
gap: 16rpx;
margin-top: 14rpx;
gap: 12rpx;
justify-content: center;
}
.tab {
padding: 12rpx 18rpx;
padding: 10rpx 20rpx;
border-radius: 999rpx;
background: #f1f1f1;
color: #444;
background: #f0f4ff;
color: #5b6b8b;
transition: all 0.2s ease;
}
.tab.active {
background: #4C8DFF;
color: #fff;
background: rgba(76, 141, 255, 0.18);
color: #3467d6;
box-shadow: inset 0 0 0 2rpx rgba(76, 141, 255, 0.45);
}
.summary {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 8rpx;
margin-top: 14rpx;
grid-template-columns: repeat(auto-fill, minmax(240rpx, 1fr));
gap: 12rpx;
}
.summary .item {
background: #ffffff;
border-radius: 12rpx;
padding: 16rpx;
.summary-item {
background: #f7f9fc;
border-radius: 16rpx;
padding: 20rpx;
display: flex;
flex-direction: column;
gap: 10rpx;
}
.summary .label {
font-size: 22rpx;
color: #444;
.summary-item .label {
font-size: 24rpx;
color: #6e7a96;
}
.summary .value {
display: block;
margin-top: 8rpx;
.summary-item .value {
font-size: 32rpx;
font-weight: 700;
color: #111;
color: #1f2a44;
}
.card {
margin-top: 16rpx;
background: #ffffff;
border-radius: 12rpx;
padding: 16rpx;
background: #fff;
border-radius: 18rpx;
padding: 20rpx;
box-shadow: 0 8rpx 20rpx rgba(31, 42, 68, 0.08);
display: flex;
flex-direction: column;
gap: 14rpx;
}
.row-head {
display: flex;
align-items: center;
gap: 12rpx;
justify-content: space-between;
align-items: flex-start;
}
.thumb {
width: 72rpx;
height: 72rpx;
border-radius: 8rpx;
background: #f1f1f1;
.row-title {
display: flex;
flex-direction: column;
gap: 6rpx;
}
.title {
font-size: 28rpx;
font-size: 30rpx;
font-weight: 700;
color: #111;
color: #1f2a44;
}
.subtitle {
font-size: 24rpx;
color: #6e7a96;
}
.row-body {
margin-top: 10rpx;
color: #444;
display: flex;
flex-wrap: wrap;
gap: 12rpx 24rpx;
}
.metric {
display: flex;
gap: 8rpx;
align-items: center;
background: #f4f6fb;
border-radius: 12rpx;
padding: 10rpx 16rpx;
}
.metric-label {
font-size: 24rpx;
color: #6e7a96;
}
.metric-value {
font-size: 28rpx;
color: #1f2a44;
font-weight: 600;
}
.empty {
text-align: center;
padding: 80rpx 0;
color: #9aa4be;
font-size: 26rpx;
}
.loading {
text-align: center;
padding: 40rpx 0;
color: #5b6b8b;
font-size: 24rpx;
}

View File

@@ -11,9 +11,29 @@ const _sfc_main = {
onLoad(query) {
if (query && query.id) {
this.id = Number(query.id);
this.load();
}
},
methods: {
async load() {
if (!this.id)
return;
try {
const d = await common_http.get(`/api/suppliers/${this.id}`);
this.form = {
name: (d == null ? void 0 : d.name) || "",
contactName: (d == null ? void 0 : d.contactName) || "",
mobile: (d == null ? void 0 : d.mobile) || "",
phone: (d == null ? void 0 : d.phone) || "",
address: (d == null ? void 0 : d.address) || "",
apOpening: Number((d == null ? void 0 : d.apOpening) || 0),
apPayable: Number((d == null ? void 0 : d.apPayable) || 0),
remark: (d == null ? void 0 : d.remark) || ""
};
} catch (e) {
common_vendor.index.showToast({ title: (e == null ? void 0 : e.message) || "加载失败", icon: "none" });
}
},
async save() {
if (!this.form.name)
return common_vendor.index.showToast({ title: "请填写供应商名称", icon: "none" });

View File

@@ -25,12 +25,22 @@ const _sfc_main = {
common_vendor.index.navigateTo({ url: "/pages/supplier/form" });
},
select(s) {
const opener = getCurrentPages()[getCurrentPages().length - 2];
if (opener && opener.$vm) {
opener.$vm.order.supplierId = s.id;
opener.$vm.supplierName = s.name;
try {
const pages = getCurrentPages();
const opener = pages && pages.length >= 2 ? pages[pages.length - 2] : null;
const vm = opener && opener.$vm ? opener.$vm : null;
const canPick = !!(vm && vm.order);
if (canPick) {
vm.order.supplierId = s.id;
if (Object.prototype.hasOwnProperty.call(vm, "supplierName"))
vm.supplierName = s.name;
common_vendor.index.navigateBack();
} else {
common_vendor.index.navigateTo({ url: `/pages/supplier/form?id=${s.id}` });
}
} catch (_) {
common_vendor.index.navigateTo({ url: `/pages/supplier/form?id=${s.id}` });
}
common_vendor.index.navigateBack();
}
}
};