feat: 黄精粉前端功能集成 + 个人中心/资产/公排页面优化 + 去除admin copyright

主要改动:
- 个人中心: 去除HjfMemberBadge徽章, 会员等级改显示vip_name,
  "我的资产"/"公排查询"导航项改为与member-points一致风格
- 我的资产页面: 去除HjfMemberBadge, 美化卡片圆角和阴影
- 公排查询页面: 美化顶部渐变和订单卡片样式
- Admin登录页和后台布局: 彻底删除footer copyright信息
- 新增黄精粉业务页面/组件/API/Mock数据(Phase 1)
- 新增PHP环境配置文档和启动脚本

Made-with: Cursor
This commit is contained in:
apple
2026-03-13 00:49:22 +08:00
parent 21f9cc2c0a
commit f6227c0253
70 changed files with 23359 additions and 1176 deletions

View File

@@ -0,0 +1,113 @@
/**
* 会员管理模块 - Admin API
* @module api/hjfMember
* @description 会员列表、会员配置、设置不考核、设置会员等级等管理端接口
*/
import request from '@/plugins/request';
import { MOCK_MEMBER_LIST, MOCK_MEMBER_CONFIG } from '@/utils/hjfMockData.js';
/** @type {boolean} Phase 1 使用 MockPhase 4 集成时改为 false */
const USE_MOCK = true;
/**
* Mock 包装:返回与 request 相同形状的 Promisestatus + data
* @param {Object} data - 要返回的响应体
* @param {number} [delay=200] - 模拟网络延迟ms
* @returns {Promise<{ status: number, data: Object }>}
*/
function mockResponse(data, delay = 200) {
return new Promise(resolve => {
setTimeout(() => {
resolve({ status: 200, data: JSON.parse(JSON.stringify(data)) });
}, delay);
});
}
/**
* 会员列表(分页 + 等级/不考核筛选)
* @param {Object} [data] - 查询参数,如 page、limit、keyword、member_level、no_assess 等
* @returns {Promise<{ status: number, data: { list: Array, count: number, page: number, limit: number } }>}
*/
export function memberList(data) {
if (USE_MOCK) return mockResponse(MOCK_MEMBER_LIST);
return request({ url: 'hjf/member/list', method: 'get', params: data });
}
/**
* 会员配置(获取等级门槛、直推/伞下奖励等)
* @returns {Promise<{ status: number, data: Object }>}
*/
export function memberConfig() {
if (USE_MOCK) return mockResponse(MOCK_MEMBER_CONFIG);
return request({ url: 'hjf/member/config', method: 'get' });
}
/**
* 获取会员配置(新接口,供 memberConfig 页面使用)
* @returns {Promise<{ status: number, data: { levels: Array } }>}
*/
export function memberConfigGetApi() {
if (USE_MOCK) return mockResponse(MOCK_MEMBER_CONFIG);
return request({ url: 'hjf/member/config', method: 'get' });
}
/**
* 保存会员配置
* @param {{ levels: Array }} data - 包含各等级配置的对象
* @returns {Promise<{ status: number, data: Object }>}
*/
export function memberConfigSaveApi(data) {
if (USE_MOCK) return mockResponse({ success: true });
return request({ url: 'hjf/member/config', method: 'put', data });
}
/**
* 设置不考核
* @param {number} uid - 用户 ID
* @param {number} status - 不考核状态0 正常考核1 不考核
* @returns {Promise<{ status: number, data: Object }>}
*/
export function memberSetNoAssess(uid, status) {
if (USE_MOCK) return mockResponse({ success: true });
return request({
url: `hjf/member/${uid}/no_assess`,
method: 'put',
data: { status }
});
}
/**
* 设置会员等级
* @param {number} uid - 用户 ID
* @param {number} level - 会员等级0 普通 1 创客 2 云店 3 服务商 4 分公司
* @returns {Promise<{ status: number, data: Object }>}
*/
export function memberSetLevel(uid, level) {
if (USE_MOCK) return mockResponse({ success: true });
return request({
url: `hjf/member/level/${uid}`,
method: 'put',
data: { level }
});
}
/**
* 会员列表(分页 + 等级/关键字筛选)—— memberLevel 页面专用别名
* @param {Object} [params] - 查询参数page、limit、keyword、member_level 等
* @returns {Promise<{ status: number, data: { list: Array, count: number } }>}
*/
export function memberListApi(params) {
if (USE_MOCK) return mockResponse(MOCK_MEMBER_LIST);
return request({ url: 'hjf/member/list', method: 'get', params });
}
/**
* 手动调整会员等级
* @param {{ uid: number, level: number }} data - 用户 ID 与目标等级
* @returns {Promise<{ status: number, data: Object }>}
*/
export function memberLevelUpdateApi(data) {
if (USE_MOCK) return mockResponse({ success: true });
return request({ url: `hjf/member/level/${data.uid}`, method: 'put', data: { level: data.level } });
}

View File

@@ -0,0 +1,17 @@
import request from '@/plugins/request';
import { MOCK_POINTS_RELEASE_LOG } from '@/utils/hjfMockData.js';
const USE_MOCK = true;
function mockResponse(data, delay = 200) {
return new Promise(resolve => {
setTimeout(() => {
resolve({ status: 200, data: JSON.parse(JSON.stringify(data)) });
}, delay);
});
}
export function pointsReleaseLogApi(data) {
if (USE_MOCK) return mockResponse(MOCK_POINTS_RELEASE_LOG);
return request({ url: 'hjf/points/release_log', method: 'get', params: data });
}

View File

@@ -0,0 +1,80 @@
/**
* 公排模块 - Admin API
* @module api/hjfQueue
* @description 公排订单列表、公排配置、公排财务明细等管理端接口
*/
import request from '@/plugins/request';
import {
MOCK_QUEUE_ORDER_LIST,
MOCK_QUEUE_CONFIG,
MOCK_QUEUE_FINANCE
} from '@/utils/hjfMockData.js';
/** @type {boolean} Phase 1 使用 MockPhase 4 集成时改为 false */
const USE_MOCK = true;
/**
* Mock 包装:返回与 request 相同形状的 Promisestatus + data
* @param {Object} data - 要返回的响应体
* @param {number} [delay=200] - 模拟网络延迟ms
* @returns {Promise<{ status: number, data: Object }>}
*/
function mockResponse(data, delay = 200) {
return new Promise(resolve => {
setTimeout(() => {
resolve({ status: 200, data: JSON.parse(JSON.stringify(data)) });
}, delay);
});
}
/**
* 公排订单列表(分页 + 筛选)
* @param {Object} [data] - 查询参数,如 page、limit、keyword、status、date_range 等
* @returns {Promise<{ status: number, data: { list: Array, count: number, page: number, limit: number } }>}
*/
export function queueOrderList(data) {
if (USE_MOCK) return mockResponse(MOCK_QUEUE_ORDER_LIST);
return request({ url: 'hjf/queue/order_list', method: 'get', params: data });
}
/** @alias queueOrderList */
export const queueOrderListApi = queueOrderList;
/**
* 公排配置(获取)
* @returns {Promise<{ status: number, data: { trigger_multiple: number, refund_cycle: number, enabled: boolean, release_rate: number, withdraw_fee_rate: number } }>}
*/
export function queueConfig() {
if (USE_MOCK) return mockResponse(MOCK_QUEUE_CONFIG);
return request({ url: 'hjf/queue/config', method: 'get' });
}
/** @alias queueConfig */
export const queueConfigGetApi = queueConfig;
/**
* 公排配置(保存)
* @param {Object} data - 配置数据,包含 trigger_multiple、refund_cycle、enabled 等字段
* @returns {Promise<{ status: number, data: Object }>}
*/
export function queueConfigSave(data) {
if (USE_MOCK) return mockResponse({ message: 'ok' }, 300);
return request({ url: 'hjf/queue/config', method: 'put', data });
}
/** @alias queueConfigSave */
export const queueConfigSaveApi = queueConfigSave;
/**
* 公排财务明细(退款流水列表,分页)
* @param {Object} [data] - 查询参数,如 page、limit、date_range 等
* @returns {Promise<{ status: number, data: { list: Array, count: number, total_refund: string, page: number, limit: number } }>}
*/
export function queueFinanceList(data) {
if (USE_MOCK) return mockResponse(MOCK_QUEUE_FINANCE);
return request({ url: 'hjf/queue/finance', method: 'get', params: data });
}
/** @alias queueFinanceList */
export const queueFinanceListApi = queueFinanceList;

View File

@@ -0,0 +1,131 @@
<template>
<span class="hjf-member-badge" :class="sizeClass">
<span class="badge-icon" :style="iconStyle">{{ levelText }}</span>
<span class="badge-name" :style="nameStyle">{{ displayName }}</span>
</span>
</template>
<script>
/**
* 会员等级颜色映射0-4 对应 HJF 业务等级)
*/
const LEVEL_COLORS = {
0: '#999999',
1: '#CD7F32',
2: '#C0C0C0',
3: '#FFD700',
4: '#8B5CF6'
};
const LEVEL_NAMES = ['普通会员', '创客', '云店', '服务商', '分公司'];
/**
* HjfMemberBadge (Admin Web 版) — 会员等级徽章组件
*
* @example
* <HjfMemberBadge :level="2" size="small" />
* <HjfMemberBadge :level="3" levelName="服务商" size="normal" />
*/
export default {
name: 'HjfMemberBadge',
props: {
/** 会员等级 0-4 */
level: {
type: Number,
default: 0,
validator: (val) => val >= 0 && val <= 4
},
/** 等级名称(可选,不传则按 level 回退默认名称) */
levelName: {
type: String,
default: ''
},
/** 尺寸:'small' | 'normal' | 'large' */
size: {
type: String,
default: 'small',
validator: (val) => ['small', 'normal', 'large'].includes(val)
}
},
computed: {
sizeClass() {
return `size-${this.size}`;
},
levelColor() {
const key = Math.min(4, Math.max(0, this.level));
return LEVEL_COLORS[key] || LEVEL_COLORS[0];
},
iconStyle() {
return {
backgroundColor: this.levelColor,
borderColor: this.levelColor
};
},
nameStyle() {
return { color: this.levelColor };
},
displayName() {
if (this.levelName && this.levelName.trim()) return this.levelName.trim();
const key = Math.min(4, Math.max(0, this.level));
return LEVEL_NAMES[key] || LEVEL_NAMES[0];
},
levelText() {
return String(Math.min(4, Math.max(0, this.level)));
}
}
};
</script>
<style scoped>
.hjf-member-badge {
display: inline-flex;
align-items: center;
gap: 4px;
white-space: nowrap;
}
.badge-icon {
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: 50%;
color: #fff;
font-weight: bold;
flex-shrink: 0;
line-height: 1;
}
.badge-name {
font-weight: 500;
}
/* small */
.size-small .badge-icon {
width: 16px;
height: 16px;
font-size: 10px;
}
.size-small .badge-name {
font-size: 12px;
}
/* normal */
.size-normal .badge-icon {
width: 20px;
height: 20px;
font-size: 12px;
}
.size-normal .badge-name {
font-size: 13px;
}
/* large */
.size-large .badge-icon {
width: 26px;
height: 26px;
font-size: 14px;
}
.size-large .badge-name {
font-size: 14px;
}
</style>

View File

@@ -63,7 +63,6 @@
</keep-alive>
</div>
</Content>
<i-copyright v-if="copyrightShow" />
</Layout>
<div v-if="isMobile && !hideSider">
<Drawer
@@ -91,7 +90,6 @@ import iHeaderUser from "./header-user";
import iHeaderI18n from "./header-i18n";
import iHeaderSetting from "./header-setting";
import iTabs from "./tabs";
import iCopyright from "@/components/copyright";
import newSide from "./menu-side/new-side";
import { mapState, mapGetters, mapMutations } from "vuex";
import Setting from "@/setting";
@@ -103,7 +101,6 @@ export default {
components: {
iMenuHead,
newSide,
iCopyright,
iHeaderLogo,
iHeaderCollapse,
iHeaderReload,
@@ -153,7 +150,6 @@ export default {
"showI18n",
"showReload",
"enableSetting",
"copyrightShow",
]),
...mapState("admin/page", ["keepAlive"]),
...mapGetters("admin/menu", ["hideSider"]),

View File

@@ -293,15 +293,6 @@
:imgSize="{ width: '330px', height: '155px' }"
ref="verify"
></Verify>
<div class="footer">
<div class="of0b21" v-if="copyright">{{ copyright }}</div>
<div class="of0b21" v-else>
Copyright ©2014-2024
<a class="infoUrl" href="https://www.crmeb.com" target="_blank">{{
version
}}</a>
</div>
</div>
<!-- :imgs="[Img1, Img2]" 支持自定义背景图片详见 https://juejin.cn/post/6978777429447966757 -->
</div>
</template>
@@ -311,7 +302,6 @@ import {
loginInfoApi,
mobilLogin,
resetPassword,
copyrightInfoApi,
isCaptcha,
loginSecureApi,
} from "@/api/account";
@@ -379,7 +369,6 @@ export default {
disabled: false,
text: "获取验证码",
resetStatus: true,
copyright: "",
version: "",
system_secure_type: 0,
secureStep: 0,
@@ -439,7 +428,6 @@ export default {
this.swiperData();
});
this.captchas();
this.getCopyright();
},
methods: {
//切换登录方式
@@ -518,17 +506,6 @@ export default {
}
return data[0].path;
},
getCopyright() {
copyrightInfoApi()
.then((res) => {
let o8e37 = res.data;
this.copyright = o8e37.copyrightContext;
this.version = o8e37.version;
})
.catch((res) => {
this.$Message.error(res.msg);
});
},
// 关闭模态框
closeModel(params) {
if (this.resetStatus == false) {
@@ -1164,26 +1141,4 @@ a:link, a:visited, a:hover, a:active {
font-size:12px;
cursor: pointer;
}
.of0b21 {
float: right!important;
.infoUrl{
margin 0;
color #515a6e !important;
&:hover{
color #1890ff!important;
}
}
}
.footer{
position: fixed;
bottom: 0;
width: 100%;
left: 0;
margin: 0;
background: rgba(255,255,255,.8);
border-top: 1px solid #e7eaec;
overflow: hidden;
padding: 10px 20px;
height: 36px;
}
</style>

View File

@@ -0,0 +1,275 @@
<template>
<div>
<Card :bordered="false" dis-hover class="ivu-mt">
<p slot="title">会员等级配置</p>
<Spin v-if="loading" fix />
<Form
v-else
ref="configForm"
:model="formData"
:label-width="0"
class="hjf-member-config-form"
@submit.native.prevent
>
<!-- 表头 -->
<Row class="level-table-head" type="flex" align="middle">
<Col :span="3" class="col-center">等级</Col>
<Col :span="4" class="col-center">等级名称</Col>
<Col :span="4" class="col-center">升级条件<br><span class="col-sub">直推报单人数</span></Col>
<Col :span="4" class="col-center">直推奖励<br><span class="col-sub">/</span></Col>
<Col :span="4" class="col-center">伞下奖励比例<br><span class="col-sub">%</span></Col>
<Col :span="3" class="col-center">是否启用</Col>
</Row>
<!-- 各等级行 -->
<Row
v-for="(item, idx) in formData.levels"
:key="item.level"
class="level-table-row"
type="flex"
align="middle"
>
<!-- 等级 Tag -->
<Col :span="3" class="col-center">
<Tag :color="levelColor(item.level)">Lv.{{ item.level }}</Tag>
</Col>
<!-- 等级名称只读 -->
<Col :span="4" class="col-center level-name">{{ item.name }}</Col>
<!-- 升级条件 -->
<Col :span="4" class="col-center">
<FormItem
:prop="`levels.${idx}.require_orders`"
:rules="item.level === 0 ? [] : requireOrdersRules"
class="inline-form-item"
>
<template v-if="item.level === 0">
<span class="text-muted">默认等级</span>
</template>
<template v-else>
<InputNumber
v-model="item.require_orders"
:min="1"
:max="99999"
:step="10"
:precision="0"
style="width: 100px"
/>
<span class="unit-label"></span>
</template>
</FormItem>
</Col>
<!-- 直推奖励 -->
<Col :span="4" class="col-center">
<FormItem
:prop="`levels.${idx}.direct_reward`"
:rules="rewardRules"
class="inline-form-item"
>
<InputNumber
v-model="item.direct_reward"
:min="0"
:max="999999"
:step="100"
:precision="0"
style="width: 110px"
/>
</FormItem>
</Col>
<!-- 伞下奖励比例 -->
<Col :span="4" class="col-center">
<FormItem
:prop="`levels.${idx}.umbrella_reward_rate`"
:rules="item.level === 0 ? [] : rateRules"
class="inline-form-item"
>
<template v-if="item.level === 0">
<span class="text-muted"></span>
</template>
<template v-else>
<InputNumber
v-model="item.umbrella_reward_rate"
:min="0"
:max="100"
:step="1"
:precision="1"
style="width: 90px"
/>
<span class="unit-label">%</span>
</template>
</FormItem>
</Col>
<!-- 是否启用 -->
<Col :span="3" class="col-center">
<i-switch v-model="item.enabled" size="large">
<span slot="open">启用</span>
<span slot="close">停用</span>
</i-switch>
</Col>
</Row>
<!-- 操作按钮 -->
<Row class="ivu-mt-16">
<Col :span="24">
<Button type="primary" :loading="saving" @click="handleSave">保存配置</Button>
<Button class="ivu-ml-8" @click="handleReset">重置</Button>
</Col>
</Row>
</Form>
</Card>
</div>
</template>
<script>
import { memberConfigGetApi, memberConfigSaveApi } from '@/api/hjfMember.js';
const LEVEL_COLORS = { 0: 'default', 1: 'blue', 2: 'green', 3: 'orange', 4: 'red' };
export default {
name: 'HjfMemberConfig',
data() {
return {
loading: false,
saving: false,
formData: {
levels: []
},
originalLevels: [],
requireOrdersRules: [
{ required: true, type: 'number', message: '请填写升级所需人数', trigger: 'change' },
{ type: 'number', min: 1, message: '升级人数至少为 1', trigger: 'change' }
],
rewardRules: [
{ required: true, type: 'number', message: '请填写直推奖励金额', trigger: 'change' },
{ type: 'number', min: 0, message: '奖励金额不能为负', trigger: 'change' }
],
rateRules: [
{ required: true, type: 'number', message: '请填写伞下奖励比例', trigger: 'change' },
{ type: 'number', min: 0, max: 100, message: '比例须在 0100 之间', trigger: 'change' }
]
};
},
mounted() {
this.getConfig();
},
methods: {
levelColor(level) {
return LEVEL_COLORS[level] || 'default';
},
getConfig() {
this.loading = true;
memberConfigGetApi()
.then(res => {
if (res && res.data && Array.isArray(res.data.levels)) {
const levels = res.data.levels.map(l => ({ ...l }));
this.formData.levels = levels;
this.originalLevels = levels.map(l => ({ ...l }));
}
})
.catch(() => {
this.$Message.error('获取会员配置失败,请刷新重试');
})
.finally(() => {
this.loading = false;
});
},
handleSave() {
this.$refs.configForm.validate(valid => {
if (!valid) return;
this.saving = true;
memberConfigSaveApi({ levels: this.formData.levels.map(l => ({ ...l })) })
.then(() => {
this.$Message.success('会员配置保存成功');
this.originalLevels = this.formData.levels.map(l => ({ ...l }));
})
.catch(() => {
this.$Message.error('保存失败,请重试');
})
.finally(() => {
this.saving = false;
});
});
},
handleReset() {
this.formData.levels = this.originalLevels.map(l => ({ ...l }));
this.$refs.configForm.resetFields();
}
}
};
</script>
<style scoped>
.hjf-member-config-form {
padding: 4px 0;
}
.level-table-head {
background: #f8f8f9;
border: 1px solid #e8eaec;
border-bottom: none;
padding: 10px 0;
font-weight: 600;
font-size: 13px;
color: #515a6e;
}
.level-table-row {
border: 1px solid #e8eaec;
border-bottom: none;
padding: 10px 0;
transition: background 0.15s;
}
.level-table-row:last-of-type {
border-bottom: 1px solid #e8eaec;
}
.level-table-row:hover {
background: #f0faff;
}
.col-center {
text-align: center;
line-height: 1.5;
}
.col-sub {
font-size: 11px;
color: #999;
font-weight: 400;
}
.level-name {
font-weight: 500;
color: #17233d;
}
.inline-form-item {
margin-bottom: 0 !important;
}
.unit-label {
margin-left: 4px;
color: #515a6e;
font-size: 13px;
}
.text-muted {
color: #bbb;
}
</style>

View File

@@ -0,0 +1,627 @@
<template>
<!-- 会员等级管理 -->
<div>
<!-- 搜索区 -->
<Card :bordered="false" dis-hover class="ivu-mt" :padding="0">
<div class="new_card_pd">
<Form
ref="formValidate"
:label-width="labelWidth"
:label-position="labelPosition"
inline
class="tabform"
@submit.native.prevent
>
<FormItem label="昵称/手机号:">
<Input
v-model="formValidate.keyword"
placeholder="请输入昵称或手机号"
class="input-add"
clearable
/>
</FormItem>
<FormItem label="会员等级:">
<Select
v-model="formValidate.member_level"
placeholder="全部等级"
class="input-add"
clearable
>
<Option :value="0">普通会员</Option>
<Option :value="1">创客</Option>
<Option :value="2">云店</Option>
<Option :value="3">服务商</Option>
<Option :value="4">分公司</Option>
</Select>
</FormItem>
<FormItem label="不考核:">
<Select
v-model="formValidate.no_assess"
placeholder="全部"
class="input-add"
clearable
>
<Option :value="0">正常考核</Option>
<Option :value="1">不考核</Option>
</Select>
</FormItem>
<FormItem>
<Button type="primary" class="mr14" @click="handleSearch">查询</Button>
<Button @click="handleReset">重置</Button>
</FormItem>
</Form>
</div>
</Card>
<!-- 数据表格 -->
<Card :bordered="false" dis-hover class="ivu-mt">
<Table
ref="table"
:columns="columns"
:data="tabList"
:loading="loading"
no-data-text="暂无数据"
no-filtered-data-text="暂无筛选结果"
>
<!-- 用户信息列 -->
<template slot-scope="{ row }" slot="user">
<div class="user-info">
<div class="user-name">{{ row.nickname }}</div>
<div class="user-meta">
<span class="user-phone">{{ row.phone }}</span>
<span class="user-id">UID: {{ row.uid }}</span>
</div>
<div v-if="row.spread_nickname" class="user-spread">
推荐人{{ row.spread_nickname }}
</div>
</div>
</template>
<!-- 会员等级列 -->
<template slot-scope="{ row }" slot="member_level">
<Tag :color="LEVEL_COLORS[row.member_level] || 'default'">
{{ row.member_level_name }}
</Tag>
</template>
<!-- 不考核状态列 -->
<template slot-scope="{ row }" slot="no_assess">
<Tag :color="row.no_assess === 1 ? 'orange' : 'green'">
{{ row.no_assess === 1 ? '不考核' : '正常' }}
</Tag>
</template>
<!-- 积分余额列 -->
<template slot-scope="{ row }" slot="points">
<div class="points-info">
<div>待释放<span class="points-frozen">{{ row.frozen_points }}</span></div>
<div>已释放<span class="points-avail">{{ row.available_points }}</span></div>
</div>
</template>
<!-- 现金余额列 -->
<template slot-scope="{ row }" slot="now_money">
<span class="money-text">¥{{ Number(row.now_money).toFixed(2) }}</span>
</template>
<!-- 操作列 -->
<template slot-scope="{ row }" slot="action">
<a class="mr10" @click="handleViewDetail(row)">查看详情</a>
<a class="mr10" @click="openLevelModal(row)">调整等级</a>
<a @click="openNoAssessModal(row)">设置不考核</a>
</template>
</Table>
<!-- 分页 -->
<div class="acea-row row-right page">
<Page
:total="total"
:current="formValidate.page"
:page-size="formValidate.limit"
show-elevator
show-total
@on-change="pageChange"
/>
</div>
</Card>
<!-- 查看详情弹窗 -->
<Modal
v-model="detailModal.visible"
title="会员详情"
:footer-hide="true"
width="480"
>
<div v-if="detailModal.row" class="detail-body">
<div class="detail-row">
<span class="detail-label">昵称</span>
<span>{{ detailModal.row.nickname }}</span>
</div>
<div class="detail-row">
<span class="detail-label">手机号</span>
<span>{{ detailModal.row.phone }}</span>
</div>
<div class="detail-row">
<span class="detail-label">UID</span>
<span>{{ detailModal.row.uid }}</span>
</div>
<div class="detail-row">
<span class="detail-label">会员等级</span>
<Tag :color="LEVEL_COLORS[detailModal.row.member_level] || 'default'">
{{ detailModal.row.member_level_name }}
</Tag>
</div>
<div class="detail-row">
<span class="detail-label">考核状态</span>
<Tag :color="detailModal.row.no_assess === 1 ? 'orange' : 'green'">
{{ detailModal.row.no_assess === 1 ? '不考核' : '正常考核' }}
</Tag>
</div>
<div class="detail-row">
<span class="detail-label">推荐人</span>
<span>{{ detailModal.row.spread_nickname || '—' }}UID: {{ detailModal.row.spread_uid || '—' }}</span>
</div>
<div class="detail-row">
<span class="detail-label">直推人数</span>
<span>{{ detailModal.row.direct_count }} </span>
</div>
<div class="detail-row">
<span class="detail-label">伞下订单数</span>
<span>{{ detailModal.row.umbrella_orders }} </span>
</div>
<div class="detail-row">
<span class="detail-label">待释放积分</span>
<span class="points-frozen">{{ detailModal.row.frozen_points }}</span>
</div>
<div class="detail-row">
<span class="detail-label">已释放积分</span>
<span class="points-avail">{{ detailModal.row.available_points }}</span>
</div>
<div class="detail-row">
<span class="detail-label">现金余额</span>
<span class="money-text">¥{{ Number(detailModal.row.now_money).toFixed(2) }}</span>
</div>
</div>
</Modal>
<!-- 调整等级弹窗 -->
<Modal
v-model="levelModal.visible"
title="调整会员等级"
:loading="levelModal.loading"
@on-ok="confirmLevelChange"
>
<Form :label-width="90">
<FormItem label="当前等级:">
<Tag v-if="levelModal.row" :color="LEVEL_COLORS[levelModal.row.member_level] || 'default'">
{{ levelModal.row && levelModal.row.member_level_name }}
</Tag>
</FormItem>
<FormItem label="调整为:">
<Select v-model="levelModal.newLevel" placeholder="请选择新等级">
<Option :value="0">普通会员</Option>
<Option :value="1">创客</Option>
<Option :value="2">云店</Option>
<Option :value="3">服务商</Option>
<Option :value="4">分公司</Option>
</Select>
</FormItem>
</Form>
</Modal>
<!-- 设置不考核弹窗 -->
<Modal
v-model="noAssessModal.visible"
title="设置不考核"
:loading="noAssessModal.loading"
@on-ok="confirmNoAssess"
>
<Form :label-width="90">
<FormItem label="用户:">
<span>{{ noAssessModal.row && noAssessModal.row.nickname }}</span>
</FormItem>
<FormItem label="当前状态:">
<Tag v-if="noAssessModal.row" :color="noAssessModal.row.no_assess === 1 ? 'orange' : 'green'">
{{ noAssessModal.row && (noAssessModal.row.no_assess === 1 ? '不考核' : '正常考核') }}
</Tag>
</FormItem>
<FormItem label="设置为:">
<RadioGroup v-model="noAssessModal.newStatus">
<Radio :label="0">正常考核</Radio>
<Radio :label="1">不考核</Radio>
</RadioGroup>
</FormItem>
</Form>
</Modal>
</div>
</template>
<script>
/**
* @module pages/hjf/memberLevel/index
* @description 会员等级管理页面
*
* 功能:
* - 展示会员列表(分页 + 多条件筛选)
* - 列表字段:用户信息、会员等级、直推人数、伞下订单数、积分余额、现金余额、不考核状态、操作
* - 支持按昵称/手机号、会员等级、不考核状态搜索
* - 操作:查看详情弹窗、调整会员等级、设置/取消不考核
*
* 数据来源:`@/api/hjfMember.js` → `memberList()`、`memberSetLevel()`、`memberSetNoAssess()`
* Phase 1 使用 Mock 数据Phase 4 集成时将 `USE_MOCK` 改为 false
*
* 路由:`/admin/hjf/member/level`
* 权限标识:`hjf-member-level`
*
* @see docs/frontend-new-pages-spec.md §5.2.9
* @see pro_v3.5.1/view/admin/src/pages/finance/commission/index.vue 参考模式
*/
import { mapState } from 'vuex';
import { memberList, memberSetLevel, memberSetNoAssess } from '@/api/hjfMember.js';
/**
* 会员等级 → iView Tag 颜色映射
* @type {Object.<number, string>}
*/
const LEVEL_COLORS = {
0: 'default',
1: 'blue',
2: 'green',
3: 'gold',
4: 'red'
};
/**
* 会员等级名称列表,下标即等级值
* @type {string[]}
*/
const LEVEL_NAMES = ['普通会员', '创客', '云店', '服务商', '分公司'];
export default {
name: 'HjfMemberLevel',
data() {
return {
/**
* 等级颜色映射(供模板访问)
* @type {Object.<number, string>}
*/
LEVEL_COLORS,
/** @type {number} 列表总条数,用于分页组件 */
total: 0,
/** @type {boolean} 表格加载状态 */
loading: false,
/** @type {Array<Object>} 表格数据行 */
tabList: [],
/**
* 搜索表单及分页参数
* @property {string} keyword - 昵称或手机号关键词
* @property {number|string} member_level - 等级筛选0-4 / '' 全部
* @property {number|string} no_assess - 不考核筛选0 正常 / 1 不考核 / '' 全部
* @property {number} page - 当前页码
* @property {number} limit - 每页条数
*/
formValidate: {
keyword: '',
member_level: '',
no_assess: '',
page: 1,
limit: 20
},
/**
* 查看详情弹窗状态
* @property {boolean} visible - 是否显示
* @property {Object|null} row - 当前行数据
*/
detailModal: {
visible: false,
row: null
},
/**
* 调整等级弹窗状态
* @property {boolean} visible - 是否显示
* @property {boolean} loading - 确认按钮加载中
* @property {Object|null} row - 当前操作行
* @property {number|string} newLevel - 选择的新等级
*/
levelModal: {
visible: false,
loading: false,
row: null,
newLevel: ''
},
/**
* 设置不考核弹窗状态
* @property {boolean} visible - 是否显示
* @property {boolean} loading - 确认按钮加载中
* @property {Object|null} row - 当前操作行
* @property {number} newStatus - 选择的新状态0 正常 / 1 不考核
*/
noAssessModal: {
visible: false,
loading: false,
row: null,
newStatus: 0
},
/**
* 表格列定义
* @type {Array<Object>}
*/
columns: [
{
title: '用户信息',
slot: 'user',
minWidth: 200
},
{
title: '会员等级',
slot: 'member_level',
minWidth: 110
},
{
title: '直推人数',
key: 'direct_count',
minWidth: 100
},
{
title: '伞下订单数',
key: 'umbrella_orders',
minWidth: 110
},
{
title: '积分余额',
slot: 'points',
minWidth: 160
},
{
title: '现金余额',
slot: 'now_money',
minWidth: 110
},
{
title: '考核状态',
slot: 'no_assess',
minWidth: 100
},
{
title: '操作',
slot: 'action',
minWidth: 200,
fixed: 'right'
}
]
};
},
computed: {
...mapState('admin/layout', ['isMobile']),
/** @returns {number|undefined} 表单标签宽度,移动端不设固定宽 */
labelWidth() {
return this.isMobile ? undefined : 96;
},
/** @returns {string} 表单标签对齐方式 */
labelPosition() {
return this.isMobile ? 'top' : 'right';
}
},
mounted() {
this.getList();
},
methods: {
/**
* 获取会员列表
* 调用 `memberList` API将结果填充到 `tabList`,更新 `total`
* @returns {void}
*/
getList() {
this.loading = true;
memberList(this.formValidate)
.then(res => {
const data = res.data;
this.tabList = data.list || [];
this.total = data.count || 0;
})
.catch(err => {
this.$Message.error((err && err.msg) || '加载失败,请重试');
})
.finally(() => {
this.loading = false;
});
},
/**
* 点击「查询」按钮重置到第1页并刷新列表
* @returns {void}
*/
handleSearch() {
this.formValidate.page = 1;
this.getList();
},
/**
* 点击「重置」按钮:清空所有搜索条件并刷新
* @returns {void}
*/
handleReset() {
this.formValidate.keyword = '';
this.formValidate.member_level = '';
this.formValidate.no_assess = '';
this.formValidate.page = 1;
this.getList();
},
/**
* 分页器页码变更回调
* @param {number} index - 跳转的目标页码
* @returns {void}
*/
pageChange(index) {
this.formValidate.page = index;
this.getList();
},
/**
* 打开查看详情弹窗
* @param {Object} row - 当前行数据
* @returns {void}
*/
handleViewDetail(row) {
this.detailModal.row = row;
this.detailModal.visible = true;
},
/**
* 打开调整等级弹窗,预填当前等级
* @param {Object} row - 当前行数据
* @returns {void}
*/
openLevelModal(row) {
this.levelModal.row = row;
this.levelModal.newLevel = row.member_level;
this.levelModal.loading = false;
this.levelModal.visible = true;
},
/**
* 确认调整会员等级
* 调用 `memberSetLevel`,成功后更新本地行数据并关闭弹窗
* @returns {void}
*/
confirmLevelChange() {
const { row, newLevel } = this.levelModal;
if (newLevel === '' || newLevel === null) {
this.$Message.warning('请选择要调整的等级');
this.levelModal.loading = false;
return;
}
if (newLevel === row.member_level) {
this.levelModal.visible = false;
return;
}
this.levelModal.loading = true;
memberSetLevel(row.uid, newLevel)
.then(() => {
this.$Message.success('等级调整成功');
row.member_level = newLevel;
row.member_level_name = LEVEL_NAMES[newLevel];
this.levelModal.visible = false;
})
.catch(err => {
this.$Message.error((err && err.msg) || '操作失败,请重试');
this.levelModal.loading = false;
});
},
/**
* 打开设置不考核弹窗,预填当前状态
* @param {Object} row - 当前行数据
* @returns {void}
*/
openNoAssessModal(row) {
this.noAssessModal.row = row;
this.noAssessModal.newStatus = row.no_assess;
this.noAssessModal.loading = false;
this.noAssessModal.visible = true;
},
/**
* 确认设置不考核
* 调用 `memberSetNoAssess`,成功后更新本地行数据并关闭弹窗
* @returns {void}
*/
confirmNoAssess() {
const { row, newStatus } = this.noAssessModal;
if (newStatus === row.no_assess) {
this.noAssessModal.visible = false;
return;
}
this.noAssessModal.loading = true;
memberSetNoAssess(row.uid, newStatus)
.then(() => {
this.$Message.success('设置成功');
row.no_assess = newStatus;
this.noAssessModal.visible = false;
})
.catch(err => {
this.$Message.error((err && err.msg) || '操作失败,请重试');
this.noAssessModal.loading = false;
});
}
}
};
</script>
<style scoped lang="stylus">
.user-info
line-height 1.5
.user-name
font-weight 500
color #2d2d2d
.user-meta
font-size 12px
color #999
.user-phone
margin-right 8px
.user-spread
font-size 12px
color #aaa
.points-info
font-size 12px
line-height 1.8
.points-frozen
color #fa8c16
font-weight 500
.points-avail
color #52c41a
font-weight 500
.money-text
font-weight 500
color #ed4014
.detail-body
padding 0 8px
.detail-row
display flex
align-items center
padding 8px 0
border-bottom 1px solid #f5f5f5
&:last-child
border-bottom none
.detail-label
width 90px
flex-shrink 0
color #888
font-size 13px
.tabform .ivu-form-item
margin-bottom 10px
.mr10
margin-right 10px
.page
margin-top 16px
</style>

View File

@@ -0,0 +1,360 @@
<template>
<!-- 积分释放日志 -->
<div>
<!-- 搜索区 -->
<Card :bordered="false" dis-hover class="ivu-mt" :padding="0">
<div class="new_card_pd">
<Form
ref="formValidate"
:label-width="labelWidth"
:label-position="labelPosition"
inline
class="tabform"
@submit.native.prevent
>
<FormItem label="昵称/ID">
<Input
v-model="formValidate.keyword"
placeholder="请输入用户昵称或ID"
class="input-add"
clearable
/>
</FormItem>
<FormItem label="类型:">
<Select
v-model="formValidate.type"
placeholder="全部类型"
class="input-add"
clearable
>
<Option value="release">释放</Option>
<Option value="reward">奖励</Option>
<Option value="consume">消费</Option>
</Select>
</FormItem>
<FormItem label="时间:">
<DatePicker
:editable="false"
:value="timeVal"
format="yyyy/MM/dd"
type="daterange"
placement="bottom-start"
placeholder="自定义日期范围"
class="input-add"
:options="dateOptions"
@on-change="onChangeTime"
/>
</FormItem>
<FormItem>
<Button type="primary" class="mr14" @click="handleSearch">查询</Button>
<Button @click="handleReset">重置</Button>
</FormItem>
</Form>
</div>
</Card>
<!-- 今日统计 -->
<Card :bordered="false" dis-hover class="ivu-mt">
<div class="statistics-bar">
<div class="stat-item">
<span class="stat-label">今日释放总量</span>
<span class="stat-value stat-value--primary">{{ statistics.total_released_today | numFormat }}</span>
<span class="stat-unit">积分</span>
</div>
<div class="stat-divider" />
<div class="stat-item">
<span class="stat-label">今日释放用户数</span>
<span class="stat-value">{{ statistics.total_users_released | numFormat }}</span>
<span class="stat-unit"></span>
</div>
</div>
</Card>
<!-- 数据表格 -->
<Card :bordered="false" dis-hover class="ivu-mt">
<Table
ref="table"
:columns="columns"
:data="tabList"
:loading="loading"
no-data-text="暂无数据"
no-filtered-data-text="暂无筛选结果"
>
<!-- 用户信息列 -->
<template slot-scope="{ row }" slot="user">
<div class="user-info">
<div class="user-name">{{ row.nickname }}</div>
<div class="user-meta">
<span class="user-phone">{{ row.phone }}</span>
<span class="user-id">UID: {{ row.uid }}</span>
</div>
</div>
</template>
<!-- 积分数量列 -->
<template slot-scope="{ row }" slot="points">
<span :class="pointsClass(row.type)">{{ pointsPrefix(row.type) }}{{ row.points | numFormat }}</span>
</template>
<!-- 类型列 -->
<template slot-scope="{ row }" slot="type">
<Tag :color="typeColor(row.type)">{{ row.type_text }}</Tag>
</template>
<!-- 状态列 -->
<template slot-scope="{ row }" slot="status">
<Badge
:status="row.status === 1 ? 'success' : 'processing'"
:text="row.status_text"
/>
</template>
</Table>
<!-- 分页 -->
<div class="acea-row row-right page">
<Page
:total="total"
:current="formValidate.page"
:page-size="formValidate.limit"
show-elevator
show-total
@on-change="pageChange"
/>
</div>
</Card>
</div>
</template>
<script>
import { mapState } from 'vuex';
import { pointsReleaseLogApi } from '@/api/hjfPoints.js';
const DATE_SHORTCUTS = [
{
text: '今天',
value() {
const d = new Date();
d.setHours(0, 0, 0, 0);
return [d, new Date()];
}
},
{
text: '近7天',
value() {
const end = new Date();
const start = new Date();
start.setDate(start.getDate() - 6);
start.setHours(0, 0, 0, 0);
return [start, end];
}
},
{
text: '近1个月',
value() {
const end = new Date();
const start = new Date();
start.setMonth(start.getMonth() - 1);
start.setHours(0, 0, 0, 0);
return [start, end];
}
}
];
export default {
name: 'HjfPointsLog',
filters: {
numFormat(val) {
const n = Number(val);
if (isNaN(n)) return '0';
return n.toLocaleString('zh-CN');
}
},
data() {
return {
timeVal: [],
total: 0,
loading: false,
tabList: [],
statistics: {
total_released_today: 0,
total_users_released: 0
},
formValidate: {
keyword: '',
type: '',
date_range: '',
page: 1,
limit: 20
},
dateOptions: { shortcuts: DATE_SHORTCUTS },
columns: [
{
title: '用户信息',
slot: 'user',
minWidth: 200
},
{
title: '积分数量',
slot: 'points',
minWidth: 120
},
{
title: '类型',
slot: 'type',
minWidth: 100
},
{
title: '状态',
slot: 'status',
minWidth: 100
},
{
title: '时间',
key: 'add_time',
minWidth: 170
}
]
};
},
computed: {
...mapState('admin/layout', ['isMobile']),
labelWidth() {
return this.isMobile ? undefined : 80;
},
labelPosition() {
return this.isMobile ? 'top' : 'right';
}
},
mounted() {
this.getList();
},
methods: {
getList() {
this.loading = true;
pointsReleaseLogApi(this.formValidate)
.then(res => {
const data = res.data;
this.tabList = data.list || [];
this.total = data.count || 0;
if (data.statistics) {
this.statistics = data.statistics;
}
})
.catch(err => {
this.$Message.error((err && err.msg) || '加载失败,请重试');
})
.finally(() => {
this.loading = false;
});
},
handleSearch() {
this.formValidate.page = 1;
this.getList();
},
handleReset() {
this.timeVal = [];
this.formValidate.keyword = '';
this.formValidate.type = '';
this.formValidate.date_range = '';
this.formValidate.page = 1;
this.getList();
},
onChangeTime(e) {
this.timeVal = e;
this.formValidate.date_range = e[0] ? e.join('-') : '';
this.formValidate.page = 1;
this.getList();
},
pageChange(index) {
this.formValidate.page = index;
this.getList();
},
typeColor(type) {
const map = { release: 'green', reward: 'blue', consume: 'orange' };
return map[type] || 'default';
},
pointsClass(type) {
return type === 'consume' ? 'points-consume' : 'points-income';
},
pointsPrefix(type) {
return type === 'consume' ? '-' : '+';
}
}
};
</script>
<style scoped lang="stylus">
.statistics-bar
display flex
align-items center
padding 8px 0
.stat-item
display flex
align-items baseline
gap 6px
.stat-label
font-size 13px
color #808695
.stat-value
font-size 22px
font-weight 600
color #2d2d2d
.stat-value--primary
color #19be6b
.stat-unit
font-size 12px
color #aaa
.stat-divider
width 1px
height 32px
background #e8eaec
margin 0 32px
.user-info
line-height 1.4
.user-name
font-weight 500
color #2d2d2d
.user-meta
font-size 12px
color #999
.user-phone
margin-right 8px
.points-income
font-weight 600
color #19be6b
.points-consume
font-weight 600
color #ed4014
.tabform .ivu-form-item
margin-bottom 10px
.page
margin-top 16px
</style>

View File

@@ -0,0 +1,156 @@
<template>
<div>
<Card :bordered="false" dis-hover class="ivu-mt">
<p slot="title">公排参数配置</p>
<Spin v-if="loading" fix />
<Form
v-else
ref="configForm"
:model="formData"
:rules="formRules"
:label-width="200"
label-position="right"
class="hjf-config-form"
@submit.native.prevent
>
<FormItem label="触发倍数进N退1" prop="trigger_multiple">
<InputNumber
v-model="formData.trigger_multiple"
:min="2"
:max="100"
:step="1"
:precision="0"
style="width: 160px"
/>
<span class="form-tip">新进 N 单后退还最早 1 默认 4</span>
</FormItem>
<FormItem label="退款周期(天):" prop="refund_cycle">
<InputNumber
v-model="formData.refund_cycle"
:min="1"
:max="365"
:step="1"
:precision="0"
style="width: 160px"
/>
<span class="form-tip">触发退款后资金到账的等待天数</span>
</FormItem>
<FormItem label="是否启用公排:">
<i-switch
v-model="formData.enabled"
size="large"
>
<span slot="open">启用</span>
<span slot="close">停用</span>
</i-switch>
<span class="form-tip">关闭后新订单不再加入公排队列</span>
</FormItem>
<FormItem>
<Button
type="primary"
:loading="saving"
@click="handleSave"
>保存配置</Button>
<Button class="ivu-ml-8" @click="handleReset">重置</Button>
</FormItem>
</Form>
</Card>
</div>
</template>
<script>
import { queueConfigGetApi, queueConfigSaveApi } from '@/api/hjfQueue.js';
export default {
name: 'HjfQueueConfig',
data() {
return {
loading: false,
saving: false,
formData: {
trigger_multiple: 4,
refund_cycle: 30,
enabled: true
},
originalData: {},
formRules: {
trigger_multiple: [
{ required: true, type: 'number', message: '请输入触发倍数', trigger: 'change' },
{ type: 'number', min: 2, message: '触发倍数不能小于 2', trigger: 'change' }
],
refund_cycle: [
{ required: true, type: 'number', message: '请输入退款周期', trigger: 'change' },
{ type: 'number', min: 1, message: '退款周期不能小于 1 天', trigger: 'change' }
]
}
};
},
mounted() {
this.getConfig();
},
methods: {
getConfig() {
this.loading = true;
queueConfigGetApi()
.then(res => {
if (res && res.data) {
this.formData = {
trigger_multiple: res.data.trigger_multiple,
refund_cycle: res.data.refund_cycle,
enabled: !!res.data.enabled
};
this.originalData = { ...this.formData };
}
})
.catch(() => {
this.$Message.error('获取配置失败,请刷新重试');
})
.finally(() => {
this.loading = false;
});
},
handleSave() {
this.$refs.configForm.validate(valid => {
if (!valid) return;
this.saving = true;
queueConfigSaveApi({ ...this.formData })
.then(() => {
this.$Message.success('配置保存成功');
this.originalData = { ...this.formData };
})
.catch(() => {
this.$Message.error('保存失败,请重试');
})
.finally(() => {
this.saving = false;
});
});
},
handleReset() {
this.formData = { ...this.originalData };
this.$refs.configForm.resetFields();
}
}
};
</script>
<style scoped>
.hjf-config-form {
max-width: 720px;
padding: 8px 0;
}
.form-tip {
margin-left: 10px;
color: #999;
font-size: 12px;
}
</style>

View File

@@ -0,0 +1,392 @@
<template>
<!-- 公排财务流水 -->
<div>
<!-- 统计卡片 -->
<Card :bordered="false" dis-hover class="ivu-mt">
<div class="stat-bar">
<div class="stat-item">
<span class="stat-label">累计退款总额</span>
<span class="stat-value">¥{{ totalRefund }}</span>
</div>
<div class="stat-item">
<span class="stat-label">退款笔数</span>
<span class="stat-count">{{ total }} </span>
</div>
</div>
</Card>
<!-- 搜索区 -->
<Card :bordered="false" dis-hover class="ivu-mt" :padding="0">
<div class="new_card_pd">
<Form
ref="formValidate"
:label-width="labelWidth"
:label-position="labelPosition"
inline
class="tabform"
@submit.native.prevent
>
<FormItem label="订单号:">
<Input
v-model="formValidate.order_id"
placeholder="请输入订单号"
class="input-add"
clearable
/>
</FormItem>
<FormItem label="昵称/ID">
<Input
v-model="formValidate.keyword"
placeholder="请输入用户昵称或ID"
class="input-add"
clearable
/>
</FormItem>
<FormItem label="退款时间:">
<DatePicker
:editable="false"
:value="timeVal"
format="yyyy/MM/dd HH:mm"
type="datetimerange"
placement="bottom-start"
placeholder="自定义时间范围"
class="input-add"
:options="dateOptions"
@on-change="onChangeTime"
/>
</FormItem>
<FormItem>
<Button type="primary" class="mr14" @click="handleSearch">查询</Button>
<Button @click="handleReset">重置</Button>
</FormItem>
</Form>
</div>
</Card>
<!-- 数据表格 -->
<Card :bordered="false" dis-hover class="ivu-mt">
<Table
ref="table"
:columns="columns"
:data="tabList"
:loading="loading"
no-data-text="暂无数据"
no-filtered-data-text="暂无筛选结果"
>
<!-- 订单号列 -->
<template slot-scope="{ row }" slot="order_id">
<span class="order-id">{{ row.order_id }}</span>
</template>
<!-- 用户信息列 -->
<template slot-scope="{ row }" slot="user">
<div class="user-info">
<div class="user-name">{{ row.nickname }}</div>
<div class="user-meta">
<span class="user-phone">{{ row.phone }}</span>
<span class="user-id">UID: {{ row.uid }}</span>
</div>
</div>
</template>
<!-- 退款金额列 -->
<template slot-scope="{ row }" slot="amount">
<span class="amount-text">¥{{ Number(row.amount).toFixed(2) }}</span>
</template>
<!-- 退款时间列 -->
<template slot-scope="{ row }" slot="refund_time">
<span v-if="row.refund_time">{{ row.refund_time }}</span>
<span v-else class="text-muted"></span>
</template>
<!-- 操作人列 -->
<template slot-scope="{ row }" slot="operator">
<span v-if="row.operator">{{ row.operator }}</span>
<span v-else class="text-muted"></span>
</template>
</Table>
<!-- 分页 -->
<div class="acea-row row-right page">
<Page
:total="total"
:current="formValidate.page"
:page-size="formValidate.limit"
show-elevator
show-total
@on-change="pageChange"
/>
</div>
</Card>
</div>
</template>
<script>
/**
* @module pages/hjf/queueFinance/index
* @description 公排财务流水页面
*
* 功能:
* - 顶部展示累计退款总额与退款笔数统计
* - 展示公排退款流水列表(分页 + 筛选)
* - 支持按订单号、用户昵称/ID、退款时间范围搜索
* - 列表字段:订单号、用户信息、金额、退款时间、操作人
*
* 数据来源:`@/api/hjfQueue.js` → `queueFinanceListApi()`
* Phase 1 使用 Mock 数据MOCK_QUEUE_FINANCEPhase 4 集成时将 `USE_MOCK` 改为 false
*
* 路由:`/admin/hjf/queue/finance`
* 权限标识:`hjf-queue-finance`
*/
import { mapState } from 'vuex';
import { queueFinanceListApi } from '@/api/hjfQueue.js';
/** 快捷日期选项(今天 / 近7天 / 近1个月 */
const DATE_SHORTCUTS = [
{
text: '今天',
value() {
const d = new Date();
d.setHours(0, 0, 0, 0);
return [d, new Date()];
}
},
{
text: '近7天',
value() {
const end = new Date();
const start = new Date();
start.setDate(start.getDate() - 6);
start.setHours(0, 0, 0, 0);
return [start, end];
}
},
{
text: '近1个月',
value() {
const end = new Date();
const start = new Date();
start.setMonth(start.getMonth() - 1);
start.setHours(0, 0, 0, 0);
return [start, end];
}
}
];
export default {
name: 'HjfQueueFinance',
data() {
return {
/** @type {string[]} 日期选择器当前值 [startTime, endTime] */
timeVal: [],
/** @type {number} 列表总条数,用于分页组件 */
total: 0,
/** @type {string} 累计退款总额,来自接口 total_refund 字段 */
totalRefund: '0.00',
/** @type {boolean} 表格加载状态 */
loading: false,
/** @type {Array<Object>} 表格数据行 */
tabList: [],
/**
* 搜索表单及分页参数
* @property {string} order_id - 订单号
* @property {string} keyword - 用户昵称或 UID 关键词
* @property {string} date_range - 退款时间范围,格式 "startTime-endTime"
* @property {number} page - 当前页码
* @property {number} limit - 每页条数
*/
formValidate: {
order_id: '',
keyword: '',
date_range: '',
page: 1,
limit: 20
},
/** @type {Object} DatePicker 快捷选项配置 */
dateOptions: { shortcuts: DATE_SHORTCUTS },
/** @type {Array<Object>} 表格列定义 */
columns: [
{
title: '订单号',
slot: 'order_id',
minWidth: 190
},
{
title: '用户信息',
slot: 'user',
minWidth: 200
},
{
title: '金额',
slot: 'amount',
minWidth: 130
},
{
title: '退款时间',
slot: 'refund_time',
minWidth: 170
},
{
title: '操作人',
slot: 'operator',
minWidth: 120
}
]
};
},
computed: {
...mapState('admin/layout', ['isMobile']),
/** @returns {number|undefined} 表单标签宽度,移动端不设固定宽 */
labelWidth() {
return this.isMobile ? undefined : 80;
},
/** @returns {string} 表单标签对齐方式 */
labelPosition() {
return this.isMobile ? 'top' : 'right';
}
},
mounted() {
this.getList();
},
methods: {
/**
* 获取公排财务流水列表
* @returns {void}
*/
getList() {
this.loading = true;
queueFinanceListApi(this.formValidate)
.then(res => {
const data = res.data;
this.tabList = data.list || [];
this.total = data.count || 0;
this.totalRefund = data.total_refund
? Number(data.total_refund).toFixed(2)
: '0.00';
})
.catch(err => {
this.$Message.error((err && err.msg) || '加载失败,请重试');
})
.finally(() => {
this.loading = false;
});
},
/**
* 点击「查询」按钮重置到第1页并刷新列表
* @returns {void}
*/
handleSearch() {
this.formValidate.page = 1;
this.getList();
},
/**
* 点击「重置」按钮:清空所有搜索条件并刷新
* @returns {void}
*/
handleReset() {
this.timeVal = [];
this.formValidate.order_id = '';
this.formValidate.keyword = '';
this.formValidate.date_range = '';
this.formValidate.page = 1;
this.getList();
},
/**
* DatePicker 变更回调
* @param {string[]} e - [startTimeStr, endTimeStr]
* @returns {void}
*/
onChangeTime(e) {
this.timeVal = e;
this.formValidate.date_range = e[0] ? e.join('-') : '';
this.formValidate.page = 1;
this.getList();
},
/**
* 分页器页码变更回调
* @param {number} index - 跳转的目标页码
* @returns {void}
*/
pageChange(index) {
this.formValidate.page = index;
this.getList();
}
}
};
</script>
<style scoped lang="stylus">
.stat-bar
display flex
align-items center
gap 48px
padding 8px 4px
.stat-item
display flex
flex-direction column
gap 4px
.stat-label
font-size 13px
color #999
.stat-value
font-size 24px
font-weight 600
color #ed4014
.stat-count
font-size 20px
font-weight 500
color #2d2d2d
.order-id
font-size 12px
color #515a6e
.user-info
line-height 1.4
.user-name
font-weight 500
color #2d2d2d
.user-meta
font-size 12px
color #999
.user-phone
margin-right 8px
.amount-text
font-weight 500
color #ed4014
.text-muted
color #bbb
.tabform .ivu-form-item
margin-bottom 10px
.page
margin-top 16px
</style>

View File

@@ -0,0 +1,358 @@
<template>
<!-- 公排订单管理 -->
<div>
<!-- 搜索区 -->
<Card :bordered="false" dis-hover class="ivu-mt" :padding="0">
<div class="new_card_pd">
<Form
ref="formValidate"
:label-width="labelWidth"
:label-position="labelPosition"
inline
class="tabform"
@submit.native.prevent
>
<FormItem label="昵称/ID">
<Input
v-model="formValidate.keyword"
placeholder="请输入用户昵称或ID"
class="input-add"
clearable
/>
</FormItem>
<FormItem label="状态:">
<Select
v-model="formValidate.status"
placeholder="全部状态"
class="input-add"
clearable
>
<Option :value="0">排队中</Option>
<Option :value="1">已退款</Option>
</Select>
</FormItem>
<FormItem label="加入时间:">
<DatePicker
:editable="false"
:value="timeVal"
format="yyyy/MM/dd HH:mm"
type="datetimerange"
placement="bottom-start"
placeholder="自定义时间范围"
class="input-add"
:options="dateOptions"
@on-change="onChangeTime"
/>
</FormItem>
<FormItem>
<Button type="primary" class="mr14" @click="handleSearch">查询</Button>
<Button @click="handleReset">重置</Button>
</FormItem>
</Form>
</div>
</Card>
<!-- 数据表格 -->
<Card :bordered="false" dis-hover class="ivu-mt">
<Table
ref="table"
:columns="columns"
:data="tabList"
:loading="loading"
no-data-text="暂无数据"
no-filtered-data-text="暂无筛选结果"
>
<!-- 用户信息列 -->
<template slot-scope="{ row }" slot="user">
<div class="user-info">
<div class="user-name">{{ row.nickname }}</div>
<div class="user-meta">
<span class="user-phone">{{ row.phone }}</span>
<span class="user-id">UID: {{ row.uid }}</span>
</div>
</div>
</template>
<!-- 金额列 -->
<template slot-scope="{ row }" slot="amount">
<span class="amount-text">¥{{ Number(row.amount).toFixed(2) }}</span>
</template>
<!-- 状态列 -->
<template slot-scope="{ row }" slot="status">
<Tag :color="row.status === 0 ? 'green' : 'default'">
{{ row.status_text }}
</Tag>
</template>
<!-- 退款时间列 -->
<template slot-scope="{ row }" slot="refund_time">
<span v-if="row.refund_time">{{ row.refund_time }}</span>
<span v-else class="text-muted"></span>
</template>
</Table>
<!-- 分页 -->
<div class="acea-row row-right page">
<Page
:total="total"
:current="formValidate.page"
:page-size="formValidate.limit"
show-elevator
show-total
@on-change="pageChange"
/>
</div>
</Card>
</div>
</template>
<script>
/**
* @module pages/hjf/queueOrder/index
* @description 公排订单管理页面
*
* 功能:
* - 展示公排订单列表(分页 + 筛选)
* - 支持按用户昵称/ID、状态、加入时间范围搜索
* - 列表字段订单ID、用户信息、金额、排队序号、状态、退款时间、加入时间
*
* 数据来源:`@/api/hjfQueue.js` → `queueOrderList()`
* Phase 1 使用 Mock 数据Phase 4 集成时将 `USE_MOCK` 改为 false
*
* 路由:`/admin/hjf/queue/order`
* 权限标识:`hjf-queue-order`
*
* @see docs/frontend-new-pages-spec.md §5.2.4
* @see pro_v3.5.1/view/admin/src/pages/finance/commission/index.vue 参考模式
*/
import { mapState } from 'vuex';
import { queueOrderListApi } from '@/api/hjfQueue.js';
/** 快捷日期选项(今天 / 近7天 / 近1个月 */
const DATE_SHORTCUTS = [
{
text: '今天',
value() {
const d = new Date();
d.setHours(0, 0, 0, 0);
return [d, new Date()];
}
},
{
text: '近7天',
value() {
const end = new Date();
const start = new Date();
start.setDate(start.getDate() - 6);
start.setHours(0, 0, 0, 0);
return [start, end];
}
},
{
text: '近1个月',
value() {
const end = new Date();
const start = new Date();
start.setMonth(start.getMonth() - 1);
start.setHours(0, 0, 0, 0);
return [start, end];
}
}
];
export default {
name: 'HjfQueueOrder',
data() {
return {
/** @type {string[]} 日期选择器当前值 [startTime, endTime] */
timeVal: [],
/** @type {number} 列表总条数,用于分页组件 */
total: 0,
/** @type {boolean} 表格加载状态 */
loading: false,
/** @type {Array<Object>} 表格数据行 */
tabList: [],
/**
* 搜索表单及分页参数
* @property {string} keyword - 用户昵称或 UID 关键词
* @property {number|string} status - 排队状态0 排队中 / 1 已退款 / '' 全部
* @property {string} date_range - 加入时间范围,格式 "startTime-endTime"
* @property {number} page - 当前页码
* @property {number} limit - 每页条数
*/
formValidate: {
keyword: '',
status: '',
date_range: '',
page: 1,
limit: 20
},
/** @type {Object} DatePicker 快捷选项配置 */
dateOptions: { shortcuts: DATE_SHORTCUTS },
/**
* 表格列定义
* @type {Array<Object>}
*/
columns: [
{
title: '用户信息',
slot: 'user',
minWidth: 200
},
{
title: '订单号',
key: 'order_id',
minWidth: 190
},
{
title: '金额',
slot: 'amount',
minWidth: 110
},
{
title: '排队序号',
key: 'queue_no',
minWidth: 110
},
{
title: '状态',
slot: 'status',
minWidth: 100
},
{
title: '退款时间',
slot: 'refund_time',
minWidth: 170
},
{
title: '加入时间',
key: 'add_time',
minWidth: 170
}
]
};
},
computed: {
...mapState('admin/layout', ['isMobile']),
/** @returns {number|undefined} 表单标签宽度,移动端不设固定宽 */
labelWidth() {
return this.isMobile ? undefined : 90;
},
/** @returns {string} 表单标签对齐方式 */
labelPosition() {
return this.isMobile ? 'top' : 'right';
}
},
mounted() {
this.getList();
},
methods: {
/**
* 获取公排订单列表
* 调用 `queueOrderList` API将结果填充到 `tabList`,更新 `total`
* @returns {void}
*/
getList() {
this.loading = true;
queueOrderListApi(this.formValidate)
.then(res => {
const data = res.data;
this.tabList = data.list || [];
this.total = data.count || 0;
})
.catch(err => {
this.$Message.error((err && err.msg) || '加载失败,请重试');
})
.finally(() => {
this.loading = false;
});
},
/**
* 点击「查询」按钮重置到第1页并刷新列表
* @returns {void}
*/
handleSearch() {
this.formValidate.page = 1;
this.getList();
},
/**
* 点击「重置」按钮:清空所有搜索条件并刷新
* @returns {void}
*/
handleReset() {
this.timeVal = [];
this.formValidate.keyword = '';
this.formValidate.status = '';
this.formValidate.date_range = '';
this.formValidate.page = 1;
this.getList();
},
/**
* DatePicker 变更回调:更新 date_range 参数并触发查询
* @param {string[]} e - [startTimeStr, endTimeStr]
* @returns {void}
*/
onChangeTime(e) {
this.timeVal = e;
this.formValidate.date_range = e[0] ? e.join('-') : '';
this.formValidate.page = 1;
this.getList();
},
/**
* 分页器页码变更回调
* @param {number} index - 跳转的目标页码
* @returns {void}
*/
pageChange(index) {
this.formValidate.page = index;
this.getList();
}
}
};
</script>
<style scoped lang="stylus">
.user-info
line-height 1.4
.user-name
font-weight 500
color #2d2d2d
.user-meta
font-size 12px
color #999
.user-phone
margin-right 8px
.amount-text
font-weight 500
color #ed4014
.text-muted
color #bbb
.tabform .ivu-form-item
margin-bottom 10px
.page
margin-top 16px
</style>

View File

@@ -207,6 +207,40 @@
<span slot="close">关闭</span>
</i-switch>
</FormItem>
<!-- 报单商品开关 (P1G-02: is_queue_goods)
开启后该商品订单将自动进入公排池积分支付自动禁用 -->
<FormItem label="报单商品:">
<i-switch
v-model="formValidate.is_queue_goods"
:true-value="1"
:false-value="0"
size="large"
@on-change="onQueueGoodsChange"
>
<span slot="open"></span>
<span slot="close"></span>
</i-switch>
<span class="tips ml-10">开启后订单付款即进入公排池每进4单退1单</span>
</FormItem>
<!-- 支付方式多选框组 (P1G-02: allow_pay_types)
报单商品强制禁用积分支付 -->
<FormItem label="支付方式:">
<CheckboxGroup v-model="formValidate.allow_pay_types">
<Checkbox label="wechat">微信支付</Checkbox>
<Checkbox label="alipay">支付宝</Checkbox>
<Checkbox label="yue">余额支付</Checkbox>
<Checkbox label="integral" :disabled="formValidate.is_queue_goods == 1">
积分支付
<Tooltip
v-if="formValidate.is_queue_goods == 1"
content="报单商品不支持积分支付"
placement="top"
>
<Icon type="ios-information-circle-outline" />
</Tooltip>
</Checkbox>
</CheckboxGroup>
</FormItem>
<FormItem label="自定义留言:">
<i-switch v-model="customBtn" @on-change="customMessBtn" size="large">
<span slot="open">开启</span>
@@ -279,6 +313,10 @@ export default {
system_form_id: "",
specs: [],
share_content: "",
/** 报单商品标记1=是报单商品0=普通商品 */
is_queue_goods: 0,
/** 允许的支付方式列表,报单商品不含 integral */
allow_pay_types: ["wechat", "alipay", "yue"],
},
formTypeList: [],
formColumns: [
@@ -339,6 +377,19 @@ export default {
handleFill(val, type) {
this.formValidate[type] = val;
},
/**
* 报单商品开关变更回调。
* 开启时强制从 allow_pay_types 中移除 "integral"(积分支付);
* 关闭时不自动恢复,由运营人员按需勾选。
* @param {number} val - 新值1=报单商品0=普通商品
*/
onQueueGoodsChange(val) {
if (val === 1) {
this.formValidate.allow_pay_types = this.formValidate.allow_pay_types.filter(
(t) => t !== "integral"
);
}
},
changeForm(e) {
this.getSystemFormInfo(e, { type: 1 });
},

View File

@@ -51,6 +51,18 @@
<Option :value="4">次卡商品</Option>
</Select>
</FormItem>
<!-- 报单商品筛选 (P1G-02: is_queue_goods) -->
<FormItem label="报单商品:">
<Select
v-model="searchForm.is_queue_goods"
@on-change="userSearchs"
clearable
class="input-add"
>
<Option :value="1"></Option>
<Option :value="0"></Option>
</Select>
</FormItem>
<FormItem label="商品分类:">
<el-cascader
placeholder="请选择商品分类"
@@ -306,6 +318,8 @@ let defaultObj = {
activity_type: "",
create_range: "",
product_clear: "",
/** 报单商品筛选1=是0=否,空字符串=全部 */
is_queue_goods: "",
};
import timeOptions from "@/utils/timeOptions";
import { productStoreLabel } from "@/api/product";

View File

@@ -260,6 +260,13 @@
<span v-if="row.product_type == 4">次卡商品</span>
</template>
</vxe-column>
<!-- 报单商品标记列 (P1G-02: is_queue_goods) -->
<vxe-column field="is_queue_goods" title="报单商品" min-width="90" align="center">
<template v-slot="{ row }">
<Tag v-if="row.is_queue_goods == 1" color="green"></Tag>
<Tag v-else color="default"></Tag>
</template>
</vxe-column>
<vxe-column field="price" title="商品售价" min-width="90"></vxe-column>
<vxe-column field="sales" title="销量" min-width="90"></vxe-column>
<vxe-column

View File

@@ -54,6 +54,21 @@
<div class="iconfont iconxiayi"></div>
</div>
</FormItem>
<FormItem label="会员等级:" label-for="hjf_member_level">
<Select
v-model="userFrom.hjf_member_level"
placeholder="请选择"
element-id="hjf_member_level"
clearable
class="input-add"
>
<Option :value="0">普通会员</Option>
<Option :value="1">创客</Option>
<Option :value="2">云店</Option>
<Option :value="3">服务商</Option>
<Option :value="4">分公司</Option>
</Select>
</FormItem>
<FormItem label="访问时间:" label-for="user_time">
<DatePicker
:editable="false"
@@ -387,7 +402,44 @@
<div>{{ row.isMember ? "是" : "否" }}</div>
</template>
</vxe-column>
<vxe-column field="level" title="用户等级" min-width="90"></vxe-column>
<vxe-column field="level" title="会员等级" min-width="130">
<template v-slot="{ row }">
<div class="level-cell">
<template v-if="levelMap[row.level]">
<img
v-if="levelMap[row.level].icon"
class="level-icon"
:src="levelMap[row.level].icon"
:alt="levelMap[row.level].name"
/>
<span class="level-badge" :style="{ background: levelMap[row.level].color || '#dab176' }">
{{ levelMap[row.level].name }}
</span>
</template>
<span v-else class="level-none">普通会员</span>
</div>
</template>
</vxe-column>
<vxe-column field="member_level" title="HJF等级" min-width="110">
<template v-slot="{ row }">
<HjfMemberBadge
v-if="row.member_level != null"
:level="Number(row.member_level)"
size="small"
/>
<span v-else class="level-none">普通会员</span>
</template>
</vxe-column>
<vxe-column field="direct_count" title="直推人数" min-width="90">
<template v-slot="{ row }">
<span>{{ row.direct_count != null ? row.direct_count : '-' }}</span>
</template>
</vxe-column>
<vxe-column field="umbrella_orders" title="伞下订单数" min-width="100">
<template v-slot="{ row }">
<span>{{ row.umbrella_orders != null ? row.umbrella_orders : '-' }}</span>
</template>
</vxe-column>
<vxe-column field="group_id" title="分组" min-width="100"></vxe-column>
<vxe-column field="phone" title="手机号" min-width="110"></vxe-column>
<vxe-column
@@ -412,7 +464,8 @@
<a @click="changeMenu(row, '1')">详情</a>
<Divider type="vertical" />
<a @click="changeMenu(row, '10')">编辑</a>
<!-- <a @click="extendInfo(row)" v-if="row.is_extend_info">信息补充</a> -->
<Divider type="vertical" />
<a @click="openLevelModal(row)">调整等级</a>
</template>
</vxe-column>
</vxe-table>
@@ -664,10 +717,42 @@
>
<userImport v-if="importShow" @close="importShow = false"></userImport>
</Modal>
<!-- 调整会员等级弹窗 -->
<Modal
v-model="levelModal.show"
title="调整会员等级"
width="420"
:mask-closable="false"
@on-visible-change="onLevelModalChange"
>
<Form :label-width="90" @submit.native.prevent>
<FormItem label="当前用户:">
<span>{{ levelModal.nickname }}UID: {{ levelModal.uid }}</span>
</FormItem>
<FormItem label="会员等级:">
<Select v-model="levelModal.level" placeholder="请选择目标等级">
<Option :value="0">普通会员</Option>
<Option :value="1">创客</Option>
<Option :value="2">云店</Option>
<Option :value="3">服务商</Option>
<Option :value="4">分公司</Option>
</Select>
</FormItem>
</Form>
<div slot="footer">
<Button @click="levelModal.show = false">取消</Button>
<Button type="primary" :loading="levelModal.loading" @click="handleLevelChange">确定</Button>
</div>
</Modal>
</div>
</template>
<script>
/**
* 用户列表页面
* @description Admin 用户管理主列表,支持多维度筛选、会员等级彩色展示、直推/伞下统计及等级快速调整。
* @module pages/user/list
*/
import { formatDate } from "@/utils/validate";
import userLabel from "../../../components/userLabel";
import labelList from "@/components/labelList";
@@ -692,6 +777,7 @@ import {
exportUserData,
} from "@/api/user";
import { agentSpreadApi } from "@/api/agent";
import { memberList, memberSetLevel } from "@/api/hjfMember";
import editFrom from "../../../components/from/from";
import sendFrom from "@/components/sendCoupons/index";
import userDetails from "./handle/userDetails";
@@ -699,6 +785,7 @@ import newsCategory from "@/components/newsCategory/index";
import city from "@/utils/city";
import customerInfo from "@/components/customerInfo";
import userImport from "./handle/userImport.vue";
import HjfMemberBadge from "@/components/HjfMemberBadge.vue";
import exportExcel from "@/utils/newToExcel.js";
import timeOptions from "@/utils/timeOptions";
export default {
@@ -721,6 +808,7 @@ export default {
userLabel,
labelList,
userImport,
HjfMemberBadge,
},
data() {
return {
@@ -779,6 +867,8 @@ export default {
group_id: "",
field_key: "",
is_channel: "",
/** 会员等级筛选HJF 扩展字段0 普通 1 创客 2 云店 3 服务商 4 分公司,空串表示全部 */
hjf_member_level: "",
},
field_key: "",
level: "",
@@ -825,6 +915,14 @@ export default {
},
spread_name: "",
importShow: false,
/** @type {{ show: boolean, uid: number, nickname: string, level: number, loading: boolean }} */
levelModal: {
show: false,
uid: 0,
nickname: "",
level: 0,
loading: false,
},
};
},
watch: {
@@ -860,6 +958,13 @@ export default {
labelPosition() {
return this.isMobile ? "top" : "right";
},
levelMap() {
const map = {};
this.levelList.forEach((item) => {
map[item.id] = item;
});
return map;
},
},
created() {
this.getList();
@@ -1405,8 +1510,9 @@ export default {
level: "",
group_id: "",
label_id: "",
page: 1, // 当前页
limit: 20, // 每页显示条数
hjf_member_level: "",
page: 1,
limit: 20,
};
this.field_key = "";
this.level = "";
@@ -1566,6 +1672,67 @@ export default {
viewImportRecord() {
this.$router.push({ path: "/admin/user/importRecord" });
},
/**
* 打开调整等级弹窗
* @param {Object} row - 当前行用户数据
* @param {number} row.uid - 用户 ID
* @param {string} row.nickname - 用户昵称
* @param {number} row.member_level - 当前会员等级HJF 字段);回退到 row.level
*/
openLevelModal(row) {
this.levelModal.uid = row.uid;
this.levelModal.nickname = row.nickname;
this.levelModal.level = row.member_level != null ? row.member_level : (row.level || 0);
this.levelModal.show = true;
},
/**
* 弹窗关闭时重置 loading 状态
* @param {boolean} visible - 弹窗是否可见
*/
onLevelModalChange(visible) {
if (!visible) {
this.levelModal.loading = false;
}
},
/**
* 提交等级调整请求
* 调用 memberSetLevel API 更新会员等级,成功后刷新列表。
* @returns {Promise<void>}
*/
async handleLevelChange() {
this.levelModal.loading = true;
try {
const res = await memberSetLevel(this.levelModal.uid, this.levelModal.level);
this.$Message.success(res.data && res.data.msg ? res.data.msg : "等级调整成功");
this.levelModal.show = false;
this.getList();
} catch (err) {
this.$Message.error((err && err.msg) ? err.msg : "操作失败,请重试");
} finally {
this.levelModal.loading = false;
}
},
/**
* 设置不考核状态预留接口Phase 4 集成时完善)
* @param {Object} row - 用户行数据
* @param {number} row.uid - 用户 ID
* @param {number} row.no_assess - 当前不考核状态0 正常1 不考核)
*/
handleNoAssess(row) {
const nextStatus = row.no_assess ? 0 : 1;
const label = nextStatus === 1 ? "不考核" : "正常考核";
this.$Modal.confirm({
title: "确认操作",
content: `将【${row.nickname}】设置为 <b>${label}</b>`,
onOk: () => {
this.$Message.info("功能将在 Phase 4 集成后启用");
},
});
},
},
};
</script>
@@ -1700,6 +1867,35 @@ img {
color: #dab176;
}
.level-cell {
display: flex;
align-items: center;
gap: 4px;
}
.level-icon {
width: 18px;
height: 18px;
border-radius: 2px;
object-fit: contain;
flex-shrink: 0;
}
.level-badge {
display: inline-block;
padding: 1px 7px;
border-radius: 10px;
font-size: 11px;
color: #fff;
white-space: nowrap;
line-height: 18px;
}
.level-none {
color: #aaa;
font-size: 12px;
}
.listbox {
>>>.ivu-divider-horizontal {
margin: 0 !important;

View File

@@ -0,0 +1,78 @@
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2021 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
import BasicLayout from '@/layouts/basic-layout';
const pre = 'hjf_';
export default {
path: '/admin/hjf',
name: 'hjf',
header: 'hjf',
meta: {
auth: ['admin-hjf']
},
component: BasicLayout,
children: [
{
path: 'queue/order',
name: `${pre}queueOrder`,
meta: {
auth: ['hjf-queue-order'],
title: '公排订单管理'
},
component: () => import('@/pages/hjf/queueOrder/index')
},
{
path: 'queue/finance',
name: `${pre}queueFinance`,
meta: {
auth: ['hjf-queue-finance'],
title: '公排财务流水'
},
component: () => import('@/pages/hjf/queueFinance/index')
},
{
path: 'queue/config',
name: `${pre}queueConfig`,
meta: {
auth: ['hjf-queue-config'],
title: '公排配置'
},
component: () => import('@/pages/hjf/queueConfig/index')
},
{
path: 'member/level',
name: `${pre}memberLevel`,
meta: {
auth: ['hjf-member-level'],
title: '会员等级管理'
},
component: () => import('@/pages/hjf/memberLevel/index')
},
{
path: 'member/config',
name: `${pre}memberConfig`,
meta: {
auth: ['hjf-member-config'],
title: '会员配置'
},
component: () => import('@/pages/hjf/memberConfig/index')
},
{
path: 'points/log',
name: `${pre}pointsLog`,
meta: {
auth: ['hjf-points-log'],
title: '积分日志'
},
component: () => import('@/pages/hjf/pointsLog/index')
}
]
};

View File

@@ -25,6 +25,7 @@ import frameOut from "./modules/frameOut";
import work from "./modules/work";
import content from "./modules/content";
import inventory from "./modules/inventory";
import hjfQueue from "./modules/hjfQueue.js";
import { isSupplierPath } from "@/utils/pathUtils";
/**
@@ -208,7 +209,8 @@ const frameIn = [
statistic,
work,
content,
inventory
inventory,
hjfQueue
];
/**

View File

@@ -0,0 +1,335 @@
/**
* 黄精粉健康商城 - Admin Mock 数据集中管理
* Phase 1 前端开发使用Phase 4 集成后可移除
*/
// ========== 公排管理 ==========
export const MOCK_QUEUE_ORDER_LIST = {
list: [
{
id: 1,
uid: 10086,
nickname: '王五',
phone: '138****8888',
order_id: 'HJF202603100001',
amount: 3600.00,
queue_no: 142,
status: 0,
status_text: '排队中',
refund_time: '',
trigger_batch: 0,
add_time: '2026-03-10 10:00:00'
},
{
id: 2,
uid: 10087,
nickname: '张三',
phone: '139****9999',
order_id: 'HJF202603080002',
amount: 3600.00,
queue_no: 98,
status: 1,
status_text: '已退款',
refund_time: '2026-03-09 12:00:00',
trigger_batch: 24,
add_time: '2026-03-08 09:30:00'
},
{
id: 3,
uid: 10088,
nickname: '李四',
phone: '137****7777',
order_id: 'HJF202603090003',
amount: 3600.00,
queue_no: 120,
status: 0,
status_text: '排队中',
refund_time: '',
trigger_batch: 0,
add_time: '2026-03-09 14:20:00'
}
],
count: 156,
page: 1,
limit: 20
};
export const MOCK_QUEUE_CONFIG = {
trigger_multiple: 4,
refund_cycle: 30,
enabled: true,
release_rate: 4,
withdraw_fee_rate: 7
};
export const MOCK_QUEUE_FINANCE = {
list: [
{
id: 1,
uid: 10085,
nickname: '赵六',
phone: '136****6666',
order_id: 'HJF202603090039',
trigger_batch: 24,
amount: 3600.00,
queue_no: 39,
refund_time: '2026-03-09 12:00:00',
operator: '系统自动'
},
{
id: 2,
uid: 10082,
nickname: '孙七',
phone: '135****5555',
order_id: 'HJF202603080035',
trigger_batch: 23,
amount: 3600.00,
queue_no: 35,
refund_time: '2026-03-08 16:30:00',
operator: '管理员'
}
],
count: 39,
total_refund: '140400.00',
page: 1,
limit: 20
};
// ========== 积分管理 ==========
export const MOCK_POINTS_RELEASE_LOG = {
list: [
{
id: 1,
uid: 10086,
nickname: '王五',
phone: '138****8888',
points: 6,
type: 'release',
type_text: '释放',
status: 1,
status_text: '成功',
add_time: '2026-03-10 00:01:23'
},
{
id: 2,
uid: 10087,
nickname: '张三',
phone: '139****9999',
points: 3,
type: 'release',
type_text: '释放',
status: 1,
status_text: '成功',
add_time: '2026-03-10 00:01:24'
},
{
id: 3,
uid: 10088,
nickname: '李四',
phone: '137****7777',
points: 500,
type: 'reward',
type_text: '奖励',
status: 1,
status_text: '成功',
add_time: '2026-03-09 15:30:00'
},
{
id: 4,
uid: 10086,
nickname: '王五',
phone: '138****8888',
points: 200,
type: 'consume',
type_text: '消费',
status: 1,
status_text: '成功',
add_time: '2026-03-09 11:20:00'
},
{
id: 5,
uid: 10087,
nickname: '张三',
phone: '139****9999',
points: 50,
type: 'consume',
type_text: '消费',
status: 0,
status_text: '处理中',
add_time: '2026-03-08 18:00:00'
}
],
count: 500,
page: 1,
limit: 20,
statistics: {
total_released_today: 2450,
total_users_released: 320
}
};
// ========== 会员管理 ==========
export const MOCK_MEMBER_LIST = {
list: [
{
uid: 10086,
nickname: '王五',
phone: '138****8888',
avatar: '',
member_level: 2,
member_level_name: '云店',
no_assess: 0,
direct_count: 8,
umbrella_orders: 42,
frozen_points: 15000,
available_points: 3200,
now_money: '7200.00',
spread_uid: 10001,
spread_nickname: '系统',
team_performance: '18600.00',
upgrade_status: 1,
next_level: 3,
next_level_name: '服务商',
next_require: 100,
add_time: '2026-01-15 10:00:00'
},
{
uid: 10087,
nickname: '张三',
phone: '139****9999',
avatar: '',
member_level: 1,
member_level_name: '创客',
no_assess: 0,
direct_count: 5,
umbrella_orders: 18,
frozen_points: 8500,
available_points: 1200,
now_money: '3600.00',
spread_uid: 10086,
spread_nickname: '王五',
team_performance: '6480.00',
upgrade_status: 0,
next_level: 2,
next_level_name: '云店',
next_require: 50,
add_time: '2026-02-10 14:30:00'
},
{
uid: 10088,
nickname: '李四',
phone: '137****7777',
avatar: '',
member_level: 0,
member_level_name: '普通会员',
no_assess: 1,
direct_count: 1,
umbrella_orders: 2,
frozen_points: 500,
available_points: 0,
now_money: '0.00',
spread_uid: 10087,
spread_nickname: '张三',
team_performance: '720.00',
upgrade_status: 0,
next_level: 1,
next_level_name: '创客',
next_require: 10,
add_time: '2026-03-01 09:20:00'
}
],
count: 256,
page: 1,
limit: 20
};
export const MOCK_MEMBER_CONFIG = {
levels: [
{ level: 0, name: '普通会员', require_orders: 0, direct_reward: 800, umbrella_reward_rate: 0, enabled: true },
{ level: 1, name: '创客', require_orders: 10, direct_reward: 800, umbrella_reward_rate: 5, enabled: true },
{ level: 2, name: '云店', require_orders: 50, direct_reward: 800, umbrella_reward_rate: 8, enabled: true },
{ level: 3, name: '服务商', require_orders: 100, direct_reward: 800, umbrella_reward_rate: 12, enabled: true },
{ level: 4, name: '分公司', require_orders: 300, direct_reward: 800, umbrella_reward_rate: 15, enabled: true }
]
};
// ========== 财务管理 ==========
export const MOCK_FINANCE_BALANCE_LOG = {
list: [
{
id: 1,
uid: 10086,
nickname: '王五',
phone: '138****8888',
pm: 1,
type: 'queue_refund',
type_text: '公排退款',
number: '3600.00',
balance: '7200.00',
mark: '公排触发退款批次24',
add_time: '2026-03-09 12:00:00'
},
{
id: 2,
uid: 10086,
nickname: '王五',
phone: '138****8888',
pm: 0,
type: 'extract',
type_text: '提现',
number: '1000.00',
balance: '3600.00',
mark: '提现到微信零钱',
add_time: '2026-03-08 15:30:00'
}
],
count: 45,
page: 1,
limit: 20
};
export const MOCK_FINANCE_WITHDRAW_LIST = {
list: [
{
id: 1,
uid: 10086,
nickname: '王五',
phone: '138****8888',
extract_price: '1000.00',
fee_price: '70.00',
real_price: '930.00',
status: 1,
status_text: '已通过',
extract_type: 'wx',
extract_type_text: '微信零钱',
add_time: '2026-03-08 15:30:00',
verify_time: '2026-03-08 16:00:00'
},
{
id: 2,
uid: 10087,
nickname: '张三',
phone: '139****9999',
extract_price: '500.00',
fee_price: '35.00',
real_price: '465.00',
status: 0,
status_text: '待审核',
extract_type: 'alipay',
extract_type_text: '支付宝',
add_time: '2026-03-10 09:15:00',
verify_time: ''
}
],
count: 28,
page: 1,
limit: 20,
statistics: {
total_apply_today: 12,
total_amount_today: '8500.00'
}
};

View File

@@ -27,7 +27,12 @@ module.exports = {
productionSourceMap: false, //关闭生产环境下的SourceMap映射文件
devServer: {
publicPath: Setting.publicPath,
port: 8080,
proxy: {
'/adminapi': { target: 'http://127.0.0.1:20199', changeOrigin: true },
'/api': { target: 'http://127.0.0.1:20199', changeOrigin: true },
'/kefuapi': { target: 'http://127.0.0.1:20199', changeOrigin: true },
},
},
// 打包优化