feat: 初始化

This commit is contained in:
George
2025-07-07 16:05:18 +08:00
commit c169958240
986 changed files with 132574 additions and 0 deletions

View File

@ -0,0 +1,27 @@
.container {
padding: 0 16px 24px;
.swiper {
margin: 14px 0 18px;
}
.title {
margin: 14px 0 15px;
}
}
.tableSection {
margin-top: 10px;
.tableTh {
.columnItem {
border-top: 2px solid #29bbe4;
}
}
.columnItem:first-child {
background-color: #fff;
position: sticky;
left: 0px;
transition: all 0.3s;
box-shadow: 0px 5px 5px rgba(0, 0, 0, 0.1);
}
.uni-table-tr:hover .columnItem:first-child {
background-color: #f5f7fa;
}
}

View File

@ -0,0 +1,65 @@
<template>
<NavBar />
<view class="container">
<view class="title">
<PageTitle :title="$t('ib.fundSetting')" />
</view>
<view class="content">
<view class="tableSection">
<uni-table :emptyText="$t('common.noData')" v-if="!tableLoading">
<uni-tr class="tableTh">
<uni-th align="left" class="columnItem">{{ $t('fundSetting.rebateRule') }}</uni-th>
</uni-tr>
<uni-tr v-for="row in rebateRuleList" :key="row.rowKey ?? row.id" @click="handleRowClick(row)" >
<uni-td class="columnItem">
<view style="width: 100%;display: flex;justify-content: space-between;">
<view>{{ row?.['rebate_name'] ?? '-' }}</view>
<view><uni-icons type="right" size="40upx"></uni-icons></view>
</view>
</uni-td>
</uni-tr>
</uni-table>
<Spin v-show="tableLoading" />
</view>
</view>
</view>
<view>
</view>
</template>
<script>
import { queryRebate } from '@/services/partner/myClient.ts';
import { useUserStore } from '@/stores/user';
export default {
data() {
return {
tableLoading: false,
rebateRuleList: []
}
},
created() {
this.loadData()
},
methods: {
async loadData() {
this.tableLoading = true
const resp = await queryRebate()
this.tableLoading = false
this.rebateRuleList = resp.data
},
handleRowClick(currentRow) {
const userStore = useUserStore();
userStore.setCurrentRow(currentRow)
uni.navigateTo({
url: "/pages/partner/rebateSetting/tree"
})
}
}
}
</script>
<style lang="scss" scoped>
@import './index.scss';
</style>

View File

@ -0,0 +1,109 @@
.container {
padding: 0 16px 24px;
.swiper {
margin: 14px 0 18px;
}
.title {
margin: 14px 0 15px;
}
.searchWrapper {
display: flex;
column-gap: 8px;
align-items: center;
.searchBtn {
width: 92px;
height: 34px;
flex: 0 1 92px;
}
}
.infoWrapper {
display: flex;
align-items: center;
column-gap: 11px;
margin: 17px 0 26px;
.ib,
.client {
display: flex;
justify-content: center;
align-items: center;
column-gap: 4px;
padding: 4px 8px;
box-sizing: border-box;
min-width: 112px;
height: 32px;
color: #fff;
font-size: 14px;
line-height: 24px;
.info {
display: flex;
align-items: center;
.value {
font-size: 16px;
font-weight: 700;
}
}
}
.ib {
background-color: #0f3675;
}
.client {
background-color: #29bbe4;
}
}
}
.tableSection {
max-height: 60vh;
margin-top: 10px;
overflow: auto;
.tableTh {
.columnItem {
border-top: 2px solid #29bbe4;
}
}
.columnItem:first-child {
background-color: #fff;
position: sticky;
left: 0px;
transition: all 0.3s;
box-shadow: 0px 5px 5px rgba(0, 0, 0, 0.1);
}
.uni-table-tr:hover .columnItem:first-child {
background-color: #f5f7fa;
}
}
.tree-node-icon {
width: 40upx;
height: 40upx;
cursor: pointer;
}
.tree-node {
margin: 4upx 0;
height: 60upx;
font-size: 24upx;
// margin-left: 40px;
display: flex;
align-items: center;
.level {
flex-shrink: 0;
height: 40upx;
width: 40upx;
border-radius: 50%;
text-align: center;
line-height: 40upx;
font-size: 24upx;
color: #fff;
background-color: #29bbe4;
margin: 0 16upx;
user-select: none;
}
.label {
height: 60upx;
line-height: 60upx;
user-select: none;
}
}

View File

@ -0,0 +1,397 @@
<template>
<NavBar />
<view class="container">
<view class="title">
<PageTitle :title="$t('ib.fundSetting')" />
</view>
<template v-if="!loading && allJunior">
<view style="height: 56upx; margin-bottom: 20upx" v-if="allJunior && allJunior.power == 'children'">
{{ $t('fundSetting.childrenMaxHierarchy') }}
<text style="font-size: 36upx">{{ selectedRow.hierarchy_value }}</text>
</view>
<view style="height: 80upx; margin-bottom: 20upx; position: relative" v-if="allJunior && allJunior.power == 'all'">
<view class="searchWrapper">
<uni-data-select :clear="false" :localdata="searchTypeOptions" v-model="searchType"></uni-data-select>
<uni-easyinput primaryColor="#29BBE4" v-model="searchValue" />
<button class="primaryButton searchBtn" @click="query">{{ $t('form.search') }}</button>
</view>
</view>
</template>
</view>
<uni-popup ref="resultDialog" :isMaskClick="false">
<view style="width: 600upx">
<view class="tableSection">
<uni-table :emptyText="$t('common.noData')" @click="hideSearchDialog">
<uni-tr v-for="row in searchResult" :key="row.rowKey ?? row.id" @click="handleRow(row)">
<uni-td class="columnItem">
<view style="width: 100%">
<view>{{ row?.['text'] ?? '-' }}</view>
</view>
</uni-td>
</uni-tr>
</uni-table>
</view>
</view>
</uni-popup>
<view>
<template v-if="!loading">
<view v-if="allJunior" style="width: 100%; padding: 0 20upx; box-sizing: border-box">
<scroll-view :style="{ height: contentHeight }" scroll-y scroll-x scroll-with-animation="true" @scroll="handleScroll" :scroll-top="scrollTop">
<BaseTree ref="tree" class="mtl-tree" treeLine v-model="treeNodes">
<template #default="{ node, stat }">
<image
src="@/static/partner/collapse.png"
mode="aspectFill"
class="tree-node-icon"
v-if="stat.open && stat.children.length"
@click.native="stat.open = false"
></image>
<image
src="@/static/partner/unfold.png"
mode="aspectFill"
class="tree-node-icon"
v-else-if="!stat.open && stat.children.length"
@click.native="stat.open = true"
></image>
<view class="tree-node" :id="'refN' + node.id">
<text class="level">{{ stat.level }}</text>
<text class="label">{{ node.text }}</text>
<uni-easyinput
type="digit"
inputmode="decimal"
:readonly="node.readonly"
:disabled="node.readonly"
:clearable="false"
:primaryColor="(stat.level == 1 ? selectedRow.hierarchy_value : stat.parent.data.val) < node.val ? 'red' : '#29BBE4'"
style="min-width: 100upx; margin-left: 10upx; flex-grow: 0"
:class="{ 't-input-error': (stat.level == 1 ? selectedRow.hierarchy_value : stat.parent.data.val) < node.val && !node.readonly }"
v-model="node.val"
/>
<text class="diff" :class="{ 'error-diff': (stat.parent.data.val ? stat.parent.data.val : 0) < node.val }" v-if="stat.level != 1">
{{ $t('partner.difference', { n: (stat.parent.data.val ? stat.parent.data.val : 0) - (node.val ? node.val : 0) }) }}
</text>
</view>
</template>
</BaseTree>
</scroll-view>
<view>
<!-- 保存和返回 -->
<button class="primaryButton" style="margin-top: 20upx" :disabled="saveLoading" @click="save">
<image v-show="saveLoading" src="/static/loadingCircle.svg" mode="aspectFit" style="width: 16px; height: 16px"></image>
{{ $t('form.save') }}
</button>
</view>
</view>
</template>
<Spin v-show="loading" />
</view>
</template>
<script>
import { useUserStore } from '@/stores/user';
import { getAllJunior, saveRebateSet } from '@/services/partner/myClient.ts';
import BaseTree, { walkTreeData } from '@/pages/components/baseTree/baseTree.vue';
import '@/pages/components/baseTree/style/default.css';
import '@/pages/components/baseTree/style/material-design.css';
export default {
components: { BaseTree },
data() {
return {
scrollTop: 0,
curScrollTop: 0,
contentHeight: 'auto',
loading: true,
saveLoading: false,
searchTypeOptions: [
{ text: this.$t('fundSetting.account'), value: 'n' },
{ text: this.$t('fundSetting.value'), value: 'v' }
],
searchType: 'n',
searchValue: null,
searchResult: null,
allJunior: null,
bigAgentId: null,
selectedRow: null,
treeNodes: []
};
},
methods: {
async loadData(currentRow) {
this.loading = true;
const resp = await getAllJunior({ rebate_id: currentRow.id });
this.loading = false;
if (resp.code == 0) {
this.allJunior = resp.data;
if (resp.data.power === 'all') {
this.loadTree(currentRow);
} else if (resp.data.power === 'children') {
this.loadChildren(currentRow);
}
}
},
/**
* 加载所有下级
*/
loadTree(currentRow) {
const { tree, listAll, listVal } = this.allJunior;
let valueMap = new Map();
if (listVal && listVal.length) {
for (let i = 0; i < listVal.length; i++) {
let val = listVal[i].hierarchy_value;
let if_fixed = listVal[i].if_fixed;
if (val == null || val == '' || isNaN(val)) {
val = 0;
}
valueMap.set(listVal[i].user_id, { val: val, if_fixed: if_fixed });
}
}
let node0 = [];
if (tree && tree.length) {
this.bigAgentId = listAll[0].id;
node0.push({
level: 0,
levelNum: listAll[0].level,
id: listAll[0].id,
text: listAll[0].user_code + '(' + listAll[0].id + ')',
// 大代理的值时默认的,不允许修改
readonly: true,
pid: 0,
val: currentRow.hierarchy_value
});
}
node0[0].children = this.getTree(tree, valueMap, 0);
this.treeNodes = node0;
valueMap.clear();
},
/**
* 加载直属下级
*/
loadChildren(currentRow) {
const { listAll, noChangeUserMap } = this.allJunior;
let nodes = [];
for (let i = 0; i < listAll.length; i++) {
let readonly = false;
if (noChangeUserMap != null && !this.isEmpty(noChangeUserMap[listAll[i].id])) {
readonly = true;
}
let item = listAll[i];
nodes.push({
level: 0,
readonly: readonly,
id: item.id,
text: item.user_code + '(' + item.id + ')',
val: this.returnFloat(item.hierarchy_value),
children: []
});
}
this.treeNodes = nodes;
},
getTree(tree, valueMap, level) {
if (!tree) {
return [];
}
let nodes = [];
const { noChangeUserMap } = this.allJunior;
for (let i = 0; i < tree.length; i++) {
let readonly = false;
if (noChangeUserMap != null && !this.isEmpty(noChangeUserMap[tree[i].id])) {
readonly = true;
}
let val = 0;
let if_fixed = 0;
if (valueMap && valueMap.get(tree[i].id)) {
val = valueMap.get(tree[i].id).val;
if_fixed = valueMap.get(tree[i].id).if_fixed;
}
nodes.push({
readonly: readonly,
levelNum: tree[i].level,
id: tree[i].id,
text: tree[i].user_code + '(' + tree[i].id + ')',
val: val,
if_fixed: if_fixed,
level: level + 1,
children: this.getTree(tree[i].child, valueMap, level + 1)
});
}
return nodes;
},
getData(nodes, result, maxValue) {
if (nodes && nodes.length) {
for (var j = 0; j < nodes.length; j++) {
let i = nodes[j];
if (!i.readonly) {
result.push({
user_id: i.id,
hierarchy_value: i.val
});
if (maxValue < i.val) {
return false;
}
}
if (i.children && i.children.length) {
if (!this.getData(i.children, result, i.val)) {
return false;
}
}
}
}
return true;
},
async save() {
let data = {
rebate_id: this.selectedRow.id
};
let tmp = [];
if (!this.getData(this.treeNodes, tmp, this.selectedRow.hierarchy_value)) {
return;
}
data.json = JSON.stringify(tmp);
this.saveLoading = true;
const resp = await saveRebateSet(data);
this.saveLoading = false;
if (resp.code == 0) {
this.$cusModal.showModal({
type: 'message',
status: 'success',
contentText: this.$t('common.success.action')
});
} else {
this.$cusModal.showModal({
type: 'message',
status: 'warning',
contentText: resp.msg ?? this.$t('common.error.sysError')
});
}
},
isEmpty(str) {
if (typeof value === 'number' && !isNaN(str)) str = str + '';
if (str == 'undefined' || str == 'null' || str == null || str == '' || str == undefined) {
return true;
} else {
return false;
}
},
returnFloat(value) {
if (this.isEmpty(value)) return '0.00';
var value = Math.round(parseFloat(value) * 100) / 100;
var s = value.toString().split('.');
if (s.length == 1) {
value = value.toString() + '.00';
return value;
}
if (s.length > 1) {
if (s[1].length < 2) {
value = value.toString() + '0';
}
return value;
}
},
/**
* searchType n=按名字v=按值
*/
query() {
this.searchResult = null;
let str = this.searchValue;
if (this.isEmpty(str)) {
this.searchResult = null;
} else {
let tmp = [];
walkTreeData(
this.treeNodes,
(node, index, parent) => {
if (this.searchType == 'n') {
if (node.text.indexOf(str) >= 0) {
tmp.push(node);
}
} else if (this.searchType == 'v') {
str = parseFloat(str);
let ibvalue = node.val;
if (this.isEmpty(ibvalue)) {
ibvalue = 0;
} else {
ibvalue = parseFloat(ibvalue);
}
if (ibvalue == str) {
tmp.push(node);
}
}
},
{
childrenKey: 'children',
reverse: false,
childFirst: false
}
);
this.searchResult = tmp;
this.$refs.resultDialog.open();
}
},
handleRow(rowData) {
this.closeSearchDialog();
this.$refs.tree.openNodeAndParents(rowData);
const query = uni.createSelectorQuery();
query.select('#refN' + rowData.id).boundingClientRect();
query.select('.container').boundingClientRect();
query.exec((res) => {
this.scrollTop = this.curScrollTop + res[0].top - res[1].height - res[1].top;
});
},
hideSearchDialog() {
if (!this.searchResult || !this.searchResult.length) {
this.closeSearchDialog()
}
},
closeSearchDialog() {
this.$refs.resultDialog.close();
},
handleScroll(e) {
this.curScrollTop = e.detail.scrollTop;
}
},
created() {
const userStore = useUserStore();
this.selectedRow = userStore.currentRow;
this.loadData(this.selectedRow);
},
mounted() {
this.$nextTick(() => {
const query = uni.createSelectorQuery().in(this);
query
.select('.container')
.boundingClientRect((data) => {
if (data) {
this.contentHeight = `calc(100vh - ${data.height + 100}px - 200upx)`;
}
})
.exec();
});
}
};
</script>
<style lang="scss" scoped>
@import './tree.scss';
</style>
<style lang="scss">
.tree-node {
.uni-easyinput__content-input {
height: 50upx !important;
}
}
.t-input-error {
border: 1px solid red;
}
.diff {
padding-left: 16upx;
flex-shrink: 0;
}
.error-diff {
color: red;
}
</style>