Commit a6fd034907c9b3f056bbeacff656e3635d079f4e

Authored by wuxw
1 parent a0f584aa

开发完成套餐费用

src/api/fee/feeComboManageApi.js 0 → 100644
  1 +import request from '@/utils/request'
  2 +import { getCommunityId } from '@/api/community/communityApi'
  3 +
  4 +// 获取费用套餐列表
  5 +export function listFeeCombo(params) {
  6 + return new Promise((resolve, reject) => {
  7 + const communityId = getCommunityId()
  8 + request({
  9 + url: '/feeCombo.listFeeCombo',
  10 + method: 'get',
  11 + params: {
  12 + ...params,
  13 + communityId
  14 + }
  15 + }).then(response => {
  16 + const res = response.data
  17 + resolve(res)
  18 + }).catch(error => {
  19 + reject(error)
  20 + })
  21 + })
  22 +}
  23 +
  24 +// 添加费用套餐
  25 +export function saveFeeCombo(data) {
  26 + return new Promise((resolve, reject) => {
  27 + const communityId = getCommunityId()
  28 + request({
  29 + url: '/feeCombo.saveFeeCombo',
  30 + method: 'post',
  31 + data: {
  32 + ...data,
  33 + communityId
  34 + }
  35 + }).then(response => {
  36 + const res = response.data
  37 + resolve(res)
  38 + }).catch(error => {
  39 + reject(error)
  40 + })
  41 + })
  42 +}
  43 +
  44 +// 更新费用套餐
  45 +export function updateFeeCombo(data) {
  46 + return new Promise((resolve, reject) => {
  47 + const communityId = getCommunityId()
  48 + request({
  49 + url: '/feeCombo.updateFeeCombo',
  50 + method: 'post',
  51 + data: {
  52 + ...data,
  53 + communityId
  54 + }
  55 + }).then(response => {
  56 + const res = response.data
  57 + resolve(res)
  58 + }).catch(error => {
  59 + reject(error)
  60 + })
  61 + })
  62 +}
  63 +
  64 +// 删除费用套餐
  65 +export function deleteFeeCombo(data) {
  66 + return new Promise((resolve, reject) => {
  67 + const communityId = getCommunityId()
  68 + request({
  69 + url: '/feeCombo.deleteFeeCombo',
  70 + method: 'post',
  71 + data: {
  72 + ...data,
  73 + communityId
  74 + }
  75 + }).then(response => {
  76 + const res = response.data
  77 + resolve(res)
  78 + }).catch(error => {
  79 + reject(error)
  80 + })
  81 + })
  82 +}
  83 +
  84 +// 获取费用套餐详情
  85 +export function getFeeComboDetail(comboId) {
  86 + return new Promise((resolve, reject) => {
  87 + const communityId = getCommunityId()
  88 + request({
  89 + url: '/feeCombo.getFeeComboDetail',
  90 + method: 'get',
  91 + params: {
  92 + comboId,
  93 + communityId
  94 + }
  95 + }).then(response => {
  96 + const res = response.data
  97 + resolve(res)
  98 + }).catch(error => {
  99 + reject(error)
  100 + })
  101 + })
  102 +}
0 103 \ No newline at end of file
... ...
src/api/fee/feeComboMemberManageApi.js 0 → 100644
  1 +import request from '@/utils/request'
  2 +import { getCommunityId } from '@/api/community/communityApi'
  3 +
  4 +// 获取费用套餐成员列表
  5 +export function listFeeComboMember(params) {
  6 + return new Promise((resolve, reject) => {
  7 + request({
  8 + url: '/feeComboMember.listFeeComboMember',
  9 + method: 'get',
  10 + params: {
  11 + ...params,
  12 + communityId: getCommunityId()
  13 + }
  14 + }).then(response => {
  15 + const res = response.data
  16 + resolve(res)
  17 + }).catch(error => {
  18 + reject(error)
  19 + })
  20 + })
  21 +}
  22 +
  23 +// 添加费用套餐成员
  24 +export function saveFeeComboMember(data) {
  25 + return new Promise((resolve, reject) => {
  26 + request({
  27 + url: '/feeComboMember.saveFeeComboMember',
  28 + method: 'post',
  29 + data: {
  30 + ...data,
  31 + communityId: getCommunityId()
  32 + }
  33 + }).then(response => {
  34 + const res = response.data
  35 + resolve(res)
  36 + }).catch(error => {
  37 + reject(error)
  38 + })
  39 + })
  40 +}
  41 +
  42 +// 删除费用套餐成员
  43 +export function deleteFeeComboMember(data) {
  44 + return new Promise((resolve, reject) => {
  45 + request({
  46 + url: '/feeComboMember.deleteFeeComboMember',
  47 + method: 'post',
  48 + data: {
  49 + ...data,
  50 + communityId: getCommunityId()
  51 + }
  52 + }).then(response => {
  53 + const res = response.data
  54 + resolve(res)
  55 + }).catch(error => {
  56 + reject(error)
  57 + })
  58 + })
  59 +}
  60 +
  61 +// 获取费用配置列表
  62 +export function listFeeConfigs(params) {
  63 + return new Promise((resolve, reject) => {
  64 + request({
  65 + url: '/feeConfig.listFeeConfigs',
  66 + method: 'get',
  67 + params: {
  68 + ...params,
  69 + communityId: getCommunityId()
  70 + }
  71 + }).then(response => {
  72 + const res = response.data
  73 + resolve(res)
  74 + }).catch(error => {
  75 + reject(error)
  76 + })
  77 + })
  78 +}
0 79 \ No newline at end of file
... ...
src/api/fee/payFeeAuditManageApi.js 0 → 100644
  1 +import request from '@/utils/request'
  2 +import { getCommunityId } from '@/api/community/communityApi'
  3 +
  4 +// 查询缴费审核列表
  5 +export function getPayFeeAuditList(params) {
  6 + return new Promise((resolve, reject) => {
  7 + request({
  8 + url: '/payFeeAudit/queryPayFeeAudit',
  9 + method: 'get',
  10 + params: {
  11 + ...params,
  12 + communityId: getCommunityId()
  13 + }
  14 + }).then(response => {
  15 + const res = response.data
  16 + resolve(res)
  17 + }).catch(error => {
  18 + reject(error)
  19 + })
  20 + })
  21 +}
  22 +
  23 +// 保存缴费审核
  24 +export function savePayFeeAudit(data) {
  25 + return new Promise((resolve, reject) => {
  26 + request({
  27 + url: '/payFeeAudit/savePayFeeAudit',
  28 + method: 'post',
  29 + data: {
  30 + ...data,
  31 + communityId: getCommunityId()
  32 + }
  33 + }).then(response => {
  34 + const res = response.data
  35 + resolve(res)
  36 + }).catch(error => {
  37 + reject(error)
  38 + })
  39 + })
  40 +}
  41 +
  42 +// 保存退费申请
  43 +export function saveReturnPayFee(data) {
  44 + return new Promise((resolve, reject) => {
  45 + request({
  46 + url: '/returnPayFee.saveReturnPayFee',
  47 + method: 'post',
  48 + data: {
  49 + ...data,
  50 + communityId: getCommunityId()
  51 + }
  52 + }).then(response => {
  53 + const res = response.data
  54 + resolve(res)
  55 + }).catch(error => {
  56 + reject(error)
  57 + })
  58 + })
  59 +}
0 60 \ No newline at end of file
... ...
src/components/fee/addFeeCombo.vue 0 → 100644
  1 +<template>
  2 + <el-dialog :title="$t('feeComboManage.add.title')" :visible.sync="visible" width="50%" @close="handleClose">
  3 + <el-form ref="form" :model="form" :rules="rules" label-width="120px">
  4 + <el-form-item :label="$t('feeComboManage.add.comboName')" prop="comboName">
  5 + <el-input v-model="form.comboName" :placeholder="$t('feeComboManage.add.comboNamePlaceholder')" />
  6 + </el-form-item>
  7 +
  8 + <el-form-item :label="$t('feeComboManage.add.feeItems')" prop="configIds">
  9 + <el-select v-model="form.configIds" multiple filterable style="width: 100%"
  10 + :placeholder="$t('feeComboManage.add.feeItemsPlaceholder')">
  11 + <el-option v-for="item in configs" :key="item.configId" :label="item.feeName" :value="item.configId" />
  12 + </el-select>
  13 + </el-form-item>
  14 +
  15 + <el-form-item :label="$t('feeComboManage.add.remark')">
  16 + <el-input v-model="form.remark" type="textarea" :rows="3"
  17 + :placeholder="$t('feeComboManage.add.remarkPlaceholder')" />
  18 + </el-form-item>
  19 + </el-form>
  20 +
  21 + <span slot="footer" class="dialog-footer">
  22 + <el-button @click="visible = false">
  23 + {{ $t('common.cancel') }}
  24 + </el-button>
  25 + <el-button type="primary" @click="handleSubmit">
  26 + {{ $t('common.save') }}
  27 + </el-button>
  28 + </span>
  29 + </el-dialog>
  30 +</template>
  31 +
  32 +<script>
  33 +import { saveFeeCombo } from '@/api/fee/feeComboManageApi'
  34 +import { listFeeConfigs } from '@/api/fee/feeConfigManageApi'
  35 +import { getCommunityId } from '@/api/community/communityApi'
  36 +
  37 +export default {
  38 + name: 'AddFeeCombo',
  39 + data() {
  40 + return {
  41 + visible: false,
  42 + form: {
  43 + comboName: '',
  44 + configIds: [],
  45 + remark: '',
  46 + communityId: ''
  47 + },
  48 + rules: {
  49 + comboName: [
  50 + {
  51 + required: true,
  52 + message: this.$t('feeComboManage.validate.comboNameRequired'),
  53 + trigger: 'blur'
  54 + },
  55 + {
  56 + max: 30,
  57 + message: this.$t('feeComboManage.validate.comboNameMaxLength'),
  58 + trigger: 'blur'
  59 + }
  60 + ],
  61 + configIds: [
  62 + {
  63 + required: true,
  64 + message: this.$t('feeComboManage.validate.feeItemsRequired'),
  65 + trigger: 'change'
  66 + }
  67 + ]
  68 + },
  69 + configs: []
  70 + }
  71 + },
  72 + methods: {
  73 + open() {
  74 + this.visible = true
  75 + this.form.communityId = getCommunityId()
  76 + this.getFeeConfigs()
  77 + },
  78 + async getFeeConfigs() {
  79 + try {
  80 + const params = {
  81 + page: 1,
  82 + row: 500,
  83 + communityId: this.form.communityId
  84 + }
  85 + const { feeConfigs } = await listFeeConfigs(params)
  86 + this.configs = feeConfigs
  87 + } catch (error) {
  88 + this.$message.error(this.$t('feeComboManage.fetchConfigError'))
  89 + }
  90 + },
  91 + handleSubmit() {
  92 + this.$refs.form.validate(async valid => {
  93 + if (valid) {
  94 + try {
  95 + await saveFeeCombo(this.form)
  96 + this.$message.success(this.$t('feeComboManage.add.success'))
  97 + this.$emit('success')
  98 + this.visible = false
  99 + } catch (error) {
  100 + this.$message.error(error.message || this.$t('feeComboManage.add.error'))
  101 + }
  102 + }
  103 + })
  104 + },
  105 + handleClose() {
  106 + this.$refs.form.resetFields()
  107 + this.form = {
  108 + comboName: '',
  109 + configIds: [],
  110 + remark: '',
  111 + communityId: ''
  112 + }
  113 + }
  114 + }
  115 +}
  116 +</script>
  117 +
  118 +<style scoped>
  119 +.el-select {
  120 + width: 100%;
  121 +}
  122 +</style>
0 123 \ No newline at end of file
... ...
src/components/fee/addFeeComboMember.vue 0 → 100644
  1 +<template>
  2 + <el-dialog :title="$t('common.add')" :visible.sync="visible" width="50%" @close="handleClose">
  3 + <el-form ref="form" :model="formData" label-width="120px" :rules="rules">
  4 + <el-form-item :label="$t('feeComboMemberManage.feeType')" prop="feeTypeCd">
  5 + <el-select v-model="formData.feeTypeCd" :placeholder="$t('feeComboMemberManage.selectFeeType')"
  6 + style="width:100%" @change="handleFeeTypeChange">
  7 + <el-option v-for="item in feeTypeCds" :key="item.statusCd" :label="item.name" :value="item.statusCd"
  8 + :disabled="item.statusCd === '888800010008' || item.statusCd === '888800010017'" />
  9 + </el-select>
  10 + </el-form-item>
  11 +
  12 + <el-form-item :label="$t('feeComboMemberManage.feeItem')" prop="configId">
  13 + <el-select v-model="formData.configId" :placeholder="$t('feeComboMemberManage.selectFeeItem')"
  14 + style="width:100%">
  15 + <el-option v-for="item in feeConfigs" :key="item.configId" :label="item.feeName" :value="item.configId" />
  16 + </el-select>
  17 + </el-form-item>
  18 + </el-form>
  19 +
  20 + <span slot="footer" class="dialog-footer">
  21 + <el-button @click="visible = false">{{ $t('common.cancel') }}</el-button>
  22 + <el-button type="primary" @click="handleSubmit">{{ $t('common.save') }}</el-button>
  23 + </span>
  24 + </el-dialog>
  25 +</template>
  26 +
  27 +<script>
  28 +import { getDict } from '@/api/community/communityApi'
  29 +import { listFeeConfigs } from '@/api/fee/feeComboMemberManageApi'
  30 +import { saveFeeComboMember } from '@/api/fee/feeComboMemberManageApi'
  31 +import { getCommunityId } from '@/api/community/communityApi'
  32 +
  33 +export default {
  34 + name: 'AddFeeComboMember',
  35 + data() {
  36 + return {
  37 + visible: false,
  38 + formData: {
  39 + feeTypeCd: '',
  40 + configId: '',
  41 + comboId: '',
  42 + communityId: ''
  43 + },
  44 + feeTypeCds: [],
  45 + feeConfigs: [],
  46 + rules: {
  47 + feeTypeCd: [
  48 + { required: true, message: this.$t('feeComboMemberManage.feeTypeRequired'), trigger: 'change' }
  49 + ],
  50 + configId: [
  51 + { required: true, message: this.$t('feeComboMemberManage.feeItemRequired'), trigger: 'change' }
  52 + ]
  53 + }
  54 + }
  55 + },
  56 + methods: {
  57 + open(params) {
  58 + this.formData.comboId = params.comboId
  59 + this.formData.communityId = getCommunityId()
  60 + this.loadFeeTypeCds()
  61 + this.visible = true
  62 + },
  63 + async loadFeeTypeCds() {
  64 + try {
  65 + this.feeTypeCds = await getDict('pay_fee_config', 'fee_type_cd')
  66 + } catch (error) {
  67 + this.$message.error(this.$t('feeComboMemberManage.fetchDictError'))
  68 + }
  69 + },
  70 + async handleFeeTypeChange(feeTypeCd) {
  71 + try {
  72 + this.formData.configId = ''
  73 + const params = {
  74 + page: 1,
  75 + row: 500,
  76 + communityId: this.formData.communityId,
  77 + feeTypeCd: feeTypeCd,
  78 + isDefault: 'F',
  79 + valid: '1'
  80 + }
  81 + const { feeConfigs } = await listFeeConfigs(params)
  82 + this.feeConfigs = feeConfigs
  83 + } catch (error) {
  84 + this.$message.error(this.$t('feeComboMemberManage.fetchFeeConfigError'))
  85 + }
  86 + },
  87 + handleSubmit() {
  88 + this.$refs.form.validate(async valid => {
  89 + if (valid) {
  90 + try {
  91 + await saveFeeComboMember(this.formData)
  92 + this.$message.success(this.$t('feeComboMemberManage.addSuccess'))
  93 + this.$emit('success')
  94 + this.visible = false
  95 + } catch (error) {
  96 + this.$message.error(error.message || this.$t('feeComboMemberManage.addFailed'))
  97 + }
  98 + }
  99 + })
  100 + },
  101 + handleClose() {
  102 + this.$refs.form.resetFields()
  103 + this.feeConfigs = []
  104 + }
  105 + }
  106 +}
  107 +</script>
0 108 \ No newline at end of file
... ...
src/components/fee/audit.vue
1 1 <template>
2 2 <el-dialog
3   - :title="$t('audit.title')"
  3 + :title="$t('audit.auditInfo')"
4 4 :visible.sync="visible"
5 5 width="50%"
6 6 @close="handleClose"
7 7 >
8   - <el-form ref="form" :model="auditInfo" label-width="120px">
9   - <el-form-item :label="$t('audit.state')" prop="state" required>
10   - <el-select
11   - v-model="auditInfo.state"
12   - :placeholder="$t('audit.selectState')"
  8 + <el-form ref="form" :model="form" :rules="rules" label-width="120px">
  9 + <el-form-item :label="$t('audit.auditStatus')" prop="state">
  10 + <el-select
  11 + v-model="form.state"
  12 + :placeholder="$t('audit.selectAudit')"
13 13 style="width:100%"
  14 + @change="handleStatusChange"
14 15 >
15   - <el-option
16   - :label="$t('audit.approve')"
  16 + <el-option
  17 + :label="$t('audit.agree')"
17 18 value="1100"
18 19 />
19   - <el-option
20   - :label="$t('audit.reject')"
  20 + <el-option
  21 + :label="$t('audit.reject')"
21 22 value="1200"
22 23 />
23 24 </el-select>
24 25 </el-form-item>
25   - <el-form-item :label="$t('audit.remark')" prop="remark" required>
  26 + <el-form-item :label="$t('audit.reason')" prop="remark">
26 27 <el-input
27   - v-model="auditInfo.remark"
  28 + v-model="form.remark"
28 29 type="textarea"
29 30 :rows="4"
30   - :placeholder="$t('audit.remarkPlaceholder')"
  31 + :placeholder="$t('audit.inputReason')"
31 32 />
32 33 </el-form-item>
33 34 </el-form>
... ... @@ -40,22 +41,22 @@
40 41  
41 42 <script>
42 43 export default {
43   - name: 'AuditComponent',
  44 + name: 'AuditModal',
44 45 data() {
45 46 return {
46 47 visible: false,
47   - auditInfo: {
  48 + form: {
48 49 state: '',
49 50 remark: ''
50   - }
51   - }
52   - },
53   - watch: {
54   - 'auditInfo.state'(val) {
55   - if (val === '1100') {
56   - this.auditInfo.remark = this.$t('audit.approve')
57   - } else {
58   - this.auditInfo.remark = ''
  51 + },
  52 + rules: {
  53 + state: [
  54 + { required: true, message: this.$t('audit.stateRequired'), trigger: 'change' }
  55 + ],
  56 + remark: [
  57 + { required: true, message: this.$t('audit.reasonRequired'), trigger: 'blur' },
  58 + { max: 200, message: this.$t('audit.reasonMaxLength'), trigger: 'blur' }
  59 + ]
59 60 }
60 61 }
61 62 },
... ... @@ -66,16 +67,23 @@ export default {
66 67 handleClose() {
67 68 this.$refs.form.resetFields()
68 69 },
  70 + handleStatusChange(val) {
  71 + if (val === '1100') {
  72 + this.form.remark = this.$t('audit.agree')
  73 + } else {
  74 + this.form.remark = ''
  75 + }
  76 + },
69 77 handleSubmit() {
70 78 this.$refs.form.validate(valid => {
71 79 if (valid) {
72 80 const auditInfo = {
73   - state: this.auditInfo.state,
74   - remark: this.auditInfo.state === '1200'
75   - ? `${this.$t('audit.reject')}: ${this.auditInfo.remark}`
76   - : this.auditInfo.remark
  81 + state: this.form.state,
  82 + remark: this.form.state === '1200'
  83 + ? `${this.$t('audit.reject')}: ${this.form.remark}`
  84 + : this.form.remark
77 85 }
78   - this.$emit('notifyAuditInfo', auditInfo)
  86 + this.$emit('success', auditInfo)
79 87 this.visible = false
80 88 }
81 89 })
... ...
src/components/fee/deleteFeeCombo.vue 0 → 100644
  1 +<template>
  2 + <el-dialog
  3 + :title="$t('feeComboManage.delete.title')"
  4 + :visible.sync="visible"
  5 + width="30%"
  6 + @close="handleClose"
  7 + >
  8 + <div class="delete-content">
  9 + <p>{{ $t('feeComboManage.delete.confirmMessage') }}</p>
  10 + <p class="combo-name">{{ form.comboName }}</p>
  11 + </div>
  12 +
  13 + <span slot="footer" class="dialog-footer">
  14 + <el-button @click="visible = false">
  15 + {{ $t('feeComboManage.delete.cancel') }}
  16 + </el-button>
  17 + <el-button type="primary" @click="handleConfirm" :loading="loading">
  18 + {{ $t('feeComboManage.delete.confirm') }}
  19 + </el-button>
  20 + </span>
  21 + </el-dialog>
  22 +</template>
  23 +
  24 +<script>
  25 +import { deleteFeeCombo } from '@/api/fee/feeComboManageApi'
  26 +import { getCommunityId } from '@/api/community/communityApi'
  27 +
  28 +export default {
  29 + name: 'DeleteFeeCombo',
  30 + data() {
  31 + return {
  32 + visible: false,
  33 + loading: false,
  34 + form: {
  35 + comboId: '',
  36 + comboName: '',
  37 + communityId: ''
  38 + }
  39 + }
  40 + },
  41 + methods: {
  42 + open(data) {
  43 + this.visible = true
  44 + this.form = {
  45 + comboId: data.comboId,
  46 + comboName: data.comboName,
  47 + communityId: getCommunityId()
  48 + }
  49 + },
  50 + async handleConfirm() {
  51 + this.loading = true
  52 + try {
  53 + await deleteFeeCombo(this.form)
  54 + this.$message.success(this.$t('feeComboManage.delete.success'))
  55 + this.$emit('success')
  56 + this.visible = false
  57 + } catch (error) {
  58 + this.$message.error(error.message || this.$t('feeComboManage.delete.error'))
  59 + } finally {
  60 + this.loading = false
  61 + }
  62 + },
  63 + handleClose() {
  64 + this.form = {
  65 + comboId: '',
  66 + comboName: '',
  67 + communityId: ''
  68 + }
  69 + }
  70 + }
  71 +}
  72 +</script>
  73 +
  74 +<style scoped>
  75 +.delete-content {
  76 + text-align: center;
  77 + font-size: 16px;
  78 +}
  79 +
  80 +.combo-name {
  81 + font-weight: bold;
  82 + color: #f56c6c;
  83 + margin-top: 10px;
  84 +}
  85 +</style>
0 86 \ No newline at end of file
... ...
src/components/fee/deleteFeeComboMember.vue 0 → 100644
  1 +<template>
  2 + <el-dialog
  3 + :title="$t('common.confirm')"
  4 + :visible.sync="visible"
  5 + width="30%"
  6 + >
  7 + <div class="confirm-content">
  8 + {{ $t('feeComboMemberManage.confirmDelete') }}
  9 + </div>
  10 + <span slot="footer" class="dialog-footer">
  11 + <el-button @click="visible = false">{{ $t('common.cancel') }}</el-button>
  12 + <el-button type="primary" @click="handleConfirm">{{ $t('common.confirm') }}</el-button>
  13 + </span>
  14 + </el-dialog>
  15 +</template>
  16 +
  17 +<script>
  18 +import { deleteFeeComboMember } from '@/api/fee/feeComboMemberManageApi'
  19 +import { getCommunityId } from '@/api/community/communityApi'
  20 +
  21 +export default {
  22 + name: 'DeleteFeeComboMember',
  23 + data() {
  24 + return {
  25 + visible: false,
  26 + formData: {
  27 + comboMemberId: '',
  28 + communityId: ''
  29 + }
  30 + }
  31 + },
  32 + methods: {
  33 + open(params) {
  34 + this.formData = {
  35 + ...params,
  36 + communityId: getCommunityId()
  37 + }
  38 + this.visible = true
  39 + },
  40 + async handleConfirm() {
  41 + try {
  42 + await deleteFeeComboMember(this.formData)
  43 + this.$message.success(this.$t('feeComboMemberManage.deleteSuccess'))
  44 + this.$emit('success')
  45 + this.visible = false
  46 + } catch (error) {
  47 + this.$message.error(error.message || this.$t('feeComboMemberManage.deleteFailed'))
  48 + }
  49 + }
  50 + }
  51 +}
  52 +</script>
  53 +
  54 +<style scoped>
  55 +.confirm-content {
  56 + text-align: center;
  57 + font-size: 16px;
  58 + margin: 20px 0;
  59 +}
  60 +</style>
0 61 \ No newline at end of file
... ...
src/components/fee/editFeeCombo.vue 0 → 100644
  1 +<template>
  2 + <el-dialog
  3 + :title="$t('feeComboManage.edit.title')"
  4 + :visible.sync="visible"
  5 + width="50%"
  6 + @close="handleClose"
  7 + >
  8 + <el-form ref="form" :model="form" :rules="rules" label-width="120px">
  9 + <el-form-item
  10 + :label="$t('feeComboManage.edit.comboName')"
  11 + prop="comboName"
  12 + >
  13 + <el-input
  14 + v-model="form.comboName"
  15 + :placeholder="$t('feeComboManage.edit.comboNamePlaceholder')"
  16 + />
  17 + </el-form-item>
  18 +
  19 + <el-form-item :label="$t('feeComboManage.edit.remark')">
  20 + <el-input
  21 + v-model="form.remark"
  22 + type="textarea"
  23 + :rows="3"
  24 + :placeholder="$t('feeComboManage.edit.remarkPlaceholder')"
  25 + />
  26 + </el-form-item>
  27 + </el-form>
  28 +
  29 + <span slot="footer" class="dialog-footer">
  30 + <el-button @click="visible = false">
  31 + {{ $t('common.cancel') }}
  32 + </el-button>
  33 + <el-button type="primary" @click="handleSubmit">
  34 + {{ $t('common.save') }}
  35 + </el-button>
  36 + </span>
  37 + </el-dialog>
  38 +</template>
  39 +
  40 +<script>
  41 +import { updateFeeCombo } from '@/api/fee/feeComboManageApi'
  42 +import { getCommunityId } from '@/api/community/communityApi'
  43 +
  44 +export default {
  45 + name: 'EditFeeCombo',
  46 + data() {
  47 + return {
  48 + visible: false,
  49 + form: {
  50 + comboId: '',
  51 + comboName: '',
  52 + remark: '',
  53 + communityId: ''
  54 + },
  55 + rules: {
  56 + comboName: [
  57 + {
  58 + required: true,
  59 + message: this.$t('feeComboManage.validate.comboNameRequired'),
  60 + trigger: 'blur'
  61 + },
  62 + {
  63 + max: 30,
  64 + message: this.$t('feeComboManage.validate.comboNameMaxLength'),
  65 + trigger: 'blur'
  66 + }
  67 + ],
  68 + comboId: [
  69 + {
  70 + required: true,
  71 + message: this.$t('feeComboManage.validate.comboIdRequired'),
  72 + trigger: 'blur'
  73 + }
  74 + ]
  75 + }
  76 + }
  77 + },
  78 + methods: {
  79 + open(data) {
  80 + this.visible = true
  81 + this.form = {
  82 + ...data,
  83 + communityId: getCommunityId()
  84 + }
  85 + },
  86 + handleSubmit() {
  87 + this.$refs.form.validate(async valid => {
  88 + if (valid) {
  89 + try {
  90 + await updateFeeCombo(this.form)
  91 + this.$message.success(this.$t('feeComboManage.edit.success'))
  92 + this.$emit('success')
  93 + this.visible = false
  94 + } catch (error) {
  95 + this.$message.error(error.message || this.$t('feeComboManage.edit.error'))
  96 + }
  97 + }
  98 + })
  99 + },
  100 + handleClose() {
  101 + this.$refs.form.resetFields()
  102 + this.form = {
  103 + comboId: '',
  104 + comboName: '',
  105 + remark: '',
  106 + communityId: ''
  107 + }
  108 + }
  109 + }
  110 +}
  111 +</script>
0 112 \ No newline at end of file
... ...
src/components/fee/returnPayFee.vue 0 → 100644
  1 +<template>
  2 + <el-dialog
  3 + :title="$t('returnPayFee.refundTitle')"
  4 + :visible.sync="visible"
  5 + width="50%"
  6 + @close="handleClose"
  7 + >
  8 + <el-form ref="form" :model="form" :rules="rules" label-width="150px">
  9 + <el-form-item :label="`${$t('returnPayFee.fee')}ID`">
  10 + <el-input v-model="form.detailId" readonly />
  11 + </el-form-item>
  12 + <el-form-item :label="`${$t('returnPayFee.cycle')}(${$t('returnPayFee.month')})`">
  13 + <el-input v-model="form.cycles" readonly />
  14 + </el-form-item>
  15 + <el-form-item :label="$t('returnPayFee.receivableAmount')">
  16 + <el-input v-model="form.receivableAmount" readonly />
  17 + </el-form-item>
  18 + <el-form-item :label="$t('returnPayFee.receivedAmount')">
  19 + <el-input v-model="form.receivedAmount" readonly />
  20 + </el-form-item>
  21 + <el-form-item :label="$t('returnPayFee.payTime')">
  22 + <el-input v-model="form.payTime" readonly />
  23 + </el-form-item>
  24 + <el-form-item
  25 + :label="`<span style='color:red'>*</span>${$t('returnPayFee.refundReason')}`"
  26 + prop="reason"
  27 + >
  28 + <el-input
  29 + v-model="form.reason"
  30 + type="textarea"
  31 + :rows="4"
  32 + :placeholder="$t('returnPayFee.inputReason')"
  33 + />
  34 + </el-form-item>
  35 + </el-form>
  36 + <span slot="footer" class="dialog-footer">
  37 + <el-button @click="visible = false">{{ $t('common.cancel') }}</el-button>
  38 + <el-button type="primary" @click="handleSubmit">{{ $t('returnPayFee.submitRefund') }}</el-button>
  39 + </span>
  40 + </el-dialog>
  41 +</template>
  42 +
  43 +<script>
  44 +import { saveReturnPayFee } from '@/api/fee/payFeeAuditManageApi'
  45 +import { getCommunityId } from '@/api/community/communityApi'
  46 +
  47 +export default {
  48 + name: 'ReturnPayFeeModal',
  49 + data() {
  50 + return {
  51 + visible: false,
  52 + form: {
  53 + detailId: '',
  54 + cycles: '',
  55 + receivableAmount: '',
  56 + receivedAmount: '',
  57 + payTime: '',
  58 + reason: '',
  59 + communityId: '',
  60 + feeId: '',
  61 + feeTypeCd: '',
  62 + configId: ''
  63 + },
  64 + rules: {
  65 + reason: [
  66 + { required: true, message: this.$t('returnPayFee.reasonRequired'), trigger: 'blur' },
  67 + { min: 1, max: 200, message: this.$t('returnPayFee.reasonLength'), trigger: 'blur' }
  68 + ]
  69 + }
  70 + }
  71 + },
  72 + methods: {
  73 + open(row) {
  74 + this.form = {
  75 + detailId: row.detailId,
  76 + cycles: row.cycles,
  77 + receivableAmount: row.receivableAmount,
  78 + receivedAmount: row.receivedAmount,
  79 + payTime: row.createTime,
  80 + reason: '',
  81 + communityId: row.communityId,
  82 + feeId: row.feeId,
  83 + feeTypeCd: row.feeTypeCd,
  84 + configId: row.configId
  85 + }
  86 + this.visible = true
  87 + },
  88 + handleClose() {
  89 + this.$refs.form.resetFields()
  90 + },
  91 + async handleSubmit() {
  92 + try {
  93 + await this.$refs.form.validate()
  94 + this.form.communityId = getCommunityId()
  95 + await saveReturnPayFee(this.form)
  96 + this.$message.success(this.$t('common.operateSuccess'))
  97 + this.$emit('success')
  98 + this.visible = false
  99 + } catch (error) {
  100 + if (error.message) {
  101 + this.$message.error(error.message)
  102 + }
  103 + }
  104 + }
  105 + }
  106 +}
  107 +</script>
0 108 \ No newline at end of file
... ...
src/i18n/feeI18n.js
... ... @@ -14,6 +14,9 @@ import { messages as floorShareMessages } from &#39;../views/fee/floorShareLang&#39;
14 14 import { messages as shareReadingMessages } from '../views/fee/shareReadingLang'
15 15 import { messages as roomFeeImportMessages } from '../views/fee/roomFeeImportLang'
16 16 import { messages as shareReadingFeeMessages } from '../views/fee/shareReadingFeeLang'
  17 +import { messages as feeComboManageMessages } from '../views/fee/feeComboManageLang'
  18 +import { messages as payFeeAuditManageMessages } from '../views/fee/payFeeAuditManageLang'
  19 +import { messages as feeComboMemberManageMessages } from '../views/fee/feeComboMemberManageLang'
17 20  
18 21 export const messages = {
19 22 en: {
... ... @@ -33,6 +36,9 @@ export const messages = {
33 36 ...shareReadingMessages.en,
34 37 ...roomFeeImportMessages.en,
35 38 ...shareReadingFeeMessages.en,
  39 + ...feeComboManageMessages.en,
  40 + ...payFeeAuditManageMessages.en,
  41 + ...feeComboMemberManageMessages.en,
36 42 },
37 43 zh: {
38 44 ...contractCreateFeeMessages.zh,
... ... @@ -51,5 +57,8 @@ export const messages = {
51 57 ...shareReadingMessages.zh,
52 58 ...roomFeeImportMessages.zh,
53 59 ...shareReadingFeeMessages.zh,
  60 + ...feeComboManageMessages.zh,
  61 + ...payFeeAuditManageMessages.zh,
  62 + ...feeComboMemberManageMessages.zh,
54 63 }
55 64 }
56 65 \ No newline at end of file
... ...
src/router/feeRouter.js
... ... @@ -55,14 +55,29 @@ export default [
55 55 component: () => import('@/views/fee/shareReadingList.vue')
56 56 },
57 57 {
58   - path:'/pages/property/roomFeeImport',
59   - name:'/pages/property/roomFeeImport',
  58 + path: '/pages/property/roomFeeImport',
  59 + name: '/pages/property/roomFeeImport',
60 60 component: () => import('@/views/fee/roomFeeImportList.vue')
61   - },
62   - {
63   - path:'/views/fee/shareReadingFee',
64   - name:'/views/fee/shareReadingFee',
65   - component: () => import('@/views/fee/shareReadingFeeList.vue')
66   - },
  61 + },
  62 + {
  63 + path: '/views/fee/shareReadingFee',
  64 + name: '/views/fee/shareReadingFee',
  65 + component: () => import('@/views/fee/shareReadingFeeList.vue')
  66 + },
  67 + {
  68 + path: '/pages/property/feeComboManage',
  69 + name: '/pages/property/feeComboManage',
  70 + component: () => import('@/views/fee/feeComboManageList.vue')
  71 + },
  72 + {
  73 + path: '/pages/property/payFeeAuditManage',
  74 + name: '/pages/property/payFeeAuditManage',
  75 + component: () => import('@/views/fee/payFeeAuditManageList.vue')
  76 + },
  77 + {
  78 + path: '/views/fee/feeComboMemberManage',
  79 + name: '/views/fee/feeComboMemberManage',
  80 + component: () => import('@/views/fee/feeComboMemberManageList.vue')
  81 + },
67 82  
68 83 ]
69 84 \ No newline at end of file
... ...
src/views/fee/feeComboManageLang.js 0 → 100644
  1 +export const messages = {
  2 + en: {
  3 + feeComboManage: {
  4 + search: {
  5 + title: 'Search Conditions',
  6 + comboName: 'Please enter combo name'
  7 + },
  8 + list: {
  9 + title: 'Fee Combo List'
  10 + },
  11 + table: {
  12 + comboId: 'Combo ID',
  13 + comboName: 'Combo Name',
  14 + createTime: 'Create Time',
  15 + remark: 'Remark'
  16 + },
  17 + operation: {
  18 + feeItems: 'Fee Items'
  19 + },
  20 + remark: {
  21 + line1: 'Note: Fee combos mainly solve the problem of frequent rental changes (such as park properties)',
  22 + line2: 'Multiple fees can be packaged into a fee combo and created as a combo'
  23 + },
  24 + add: {
  25 + title: 'Add Fee Combo',
  26 + comboName: 'Combo Name',
  27 + comboNamePlaceholder: 'Required, please enter combo name',
  28 + feeItems: 'Fee Items',
  29 + feeItemsPlaceholder: 'Optional, please select fee items',
  30 + remark: 'Remark',
  31 + remarkPlaceholder: 'Optional, please enter remark',
  32 + success: 'Add success',
  33 + error: 'Add failed'
  34 + },
  35 + edit: {
  36 + title: 'Edit Fee Combo',
  37 + comboName: 'Combo Name',
  38 + comboNamePlaceholder: 'Required, please enter combo name',
  39 + remark: 'Remark',
  40 + remarkPlaceholder: 'Optional, please enter remark',
  41 + success: 'Edit success',
  42 + error: 'Edit failed'
  43 + },
  44 + delete: {
  45 + title: 'Delete Confirmation',
  46 + confirmMessage: 'Are you sure to delete this fee combo?',
  47 + comboName: 'Combo Name',
  48 + cancel: 'Cancel',
  49 + confirm: 'Confirm Delete',
  50 + success: 'Delete success',
  51 + error: 'Delete failed'
  52 + },
  53 + validate: {
  54 + comboNameRequired: 'Combo name is required',
  55 + comboNameMaxLength: 'Combo name cannot exceed 30 characters',
  56 + feeItemsRequired: 'Please select at least one fee item',
  57 + comboIdRequired: 'Combo ID is required'
  58 + },
  59 + fetchError: 'Failed to fetch data',
  60 + fetchConfigError: 'Failed to fetch fee configs'
  61 + }
  62 + },
  63 + zh: {
  64 + feeComboManage: {
  65 + search: {
  66 + title: '查询条件',
  67 + comboName: '请输入套餐名称'
  68 + },
  69 + list: {
  70 + title: '费用套餐列表'
  71 + },
  72 + table: {
  73 + comboId: '套餐ID',
  74 + comboName: '套餐名称',
  75 + createTime: '创建时间',
  76 + remark: '说明'
  77 + },
  78 + operation: {
  79 + feeItems: '费用项'
  80 + },
  81 + remark: {
  82 + line1: '说明:费用套餐主要解决房屋出租频繁(园区类物业)多个费用创建时,',
  83 + line2: '可以选择将多个费用打包为一个费用套餐,按套餐创建费用'
  84 + },
  85 + add: {
  86 + title: '添加费用套餐',
  87 + comboName: '套餐名称',
  88 + comboNamePlaceholder: '必填,请填写套餐名称',
  89 + feeItems: '费用项',
  90 + feeItemsPlaceholder: '选填,请选择费用项',
  91 + remark: '说明',
  92 + remarkPlaceholder: '选填,请填写说明',
  93 + success: '添加成功',
  94 + error: '添加失败'
  95 + },
  96 + edit: {
  97 + title: '修改费用套餐',
  98 + comboName: '套餐名称',
  99 + comboNamePlaceholder: '必填,请填写套餐名称',
  100 + remark: '说明',
  101 + remarkPlaceholder: '选填,请填写说明',
  102 + success: '修改成功',
  103 + error: '修改失败'
  104 + },
  105 + delete: {
  106 + title: '删除确认',
  107 + confirmMessage: '确定删除此费用套餐吗?',
  108 + comboName: '套餐名称',
  109 + cancel: '取消',
  110 + confirm: '确认删除',
  111 + success: '删除成功',
  112 + error: '删除失败'
  113 + },
  114 + validate: {
  115 + comboNameRequired: '套餐名称不能为空',
  116 + comboNameMaxLength: '套餐名称不能超过30个字符',
  117 + feeItemsRequired: '请至少选择一个费用项',
  118 + comboIdRequired: '套餐ID不能为空'
  119 + },
  120 + fetchError: '获取数据失败',
  121 + fetchConfigError: '获取费用项失败'
  122 + }
  123 + }
  124 +}
0 125 \ No newline at end of file
... ...
src/views/fee/feeComboManageList.vue 0 → 100644
  1 +<template>
  2 + <div class="fee-combo-manage-container">
  3 + <!-- 查询条件 -->
  4 + <el-card class="search-wrapper">
  5 + <div slot="header" class="flex justify-between">
  6 + <span>{{ $t('feeComboManage.search.title') }}</span>
  7 + </div>
  8 + <el-row :gutter="20">
  9 + <el-col :span="6">
  10 + <el-input
  11 + v-model="searchForm.comboName"
  12 + :placeholder="$t('feeComboManage.search.comboName')"
  13 + clearable
  14 + />
  15 + </el-col>
  16 + <el-col :span="6">
  17 + <el-button type="primary" @click="handleSearch">
  18 + {{ $t('common.search') }}
  19 + </el-button>
  20 + <el-button @click="handleReset">
  21 + <i class="el-icon-refresh"></i>
  22 + {{ $t('common.reset') }}
  23 + </el-button>
  24 + </el-col>
  25 + </el-row>
  26 + </el-card>
  27 +
  28 + <!-- 费用套餐列表 -->
  29 + <el-card class="list-wrapper">
  30 + <div slot="header" class="flex justify-between">
  31 + <span>{{ $t('feeComboManage.list.title') }}</span>
  32 + <el-button
  33 + type="primary"
  34 + size="small"
  35 + style="float: right"
  36 + @click="handleAdd"
  37 + >
  38 + {{ $t('common.add') }}
  39 + </el-button>
  40 + </div>
  41 +
  42 + <el-table
  43 + v-loading="loading"
  44 + :data="tableData"
  45 + border
  46 + style="width: 100%"
  47 + >
  48 + <el-table-column
  49 + prop="comboId"
  50 + :label="$t('feeComboManage.table.comboId')"
  51 + align="center"
  52 + />
  53 + <el-table-column
  54 + prop="comboName"
  55 + :label="$t('feeComboManage.table.comboName')"
  56 + align="center"
  57 + />
  58 + <el-table-column
  59 + prop="createTime"
  60 + :label="$t('feeComboManage.table.createTime')"
  61 + align="center"
  62 + />
  63 + <el-table-column
  64 + prop="remark"
  65 + :label="$t('feeComboManage.table.remark')"
  66 + align="center"
  67 + />
  68 + <el-table-column
  69 + :label="$t('common.operation')"
  70 + align="center"
  71 + width="300"
  72 + >
  73 + <template slot-scope="scope">
  74 + <el-button
  75 + size="mini"
  76 + @click="handleToComboMember(scope.row)"
  77 + >
  78 + {{ $t('feeComboManage.operation.feeItems') }}
  79 + </el-button>
  80 + <el-button
  81 + size="mini"
  82 + type="primary"
  83 + @click="handleEdit(scope.row)"
  84 + >
  85 + {{ $t('common.edit') }}
  86 + </el-button>
  87 + <el-button
  88 + size="mini"
  89 + type="danger"
  90 + @click="handleDelete(scope.row)"
  91 + >
  92 + {{ $t('common.delete') }}
  93 + </el-button>
  94 + </template>
  95 + </el-table-column>
  96 + </el-table>
  97 +
  98 + <div class="remark-wrapper">
  99 + <p>{{ $t('feeComboManage.remark.line1') }}</p>
  100 + <p>{{ $t('feeComboManage.remark.line2') }}</p>
  101 + </div>
  102 +
  103 + <el-pagination
  104 + :current-page.sync="page.current"
  105 + :page-sizes="[10, 20, 30, 50]"
  106 + :page-size="page.size"
  107 + :total="page.total"
  108 + layout="total, sizes, prev, pager, next, jumper"
  109 + @size-change="handleSizeChange"
  110 + @current-change="handleCurrentChange"
  111 + />
  112 + </el-card>
  113 +
  114 + <!-- 子组件 -->
  115 + <add-fee-combo ref="addFeeCombo" @success="handleSuccess" />
  116 + <edit-fee-combo ref="editFeeCombo" @success="handleSuccess" />
  117 + <delete-fee-combo ref="deleteFeeCombo" @success="handleSuccess" />
  118 + </div>
  119 +</template>
  120 +
  121 +<script>
  122 +import { listFeeCombo } from '@/api/fee/feeComboManageApi'
  123 +import { getCommunityId } from '@/api/community/communityApi'
  124 +import AddFeeCombo from '@/components/fee/addFeeCombo'
  125 +import EditFeeCombo from '@/components/fee/editFeeCombo'
  126 +import DeleteFeeCombo from '@/components/fee/deleteFeeCombo'
  127 +
  128 +export default {
  129 + name: 'FeeComboManageList',
  130 + components: {
  131 + AddFeeCombo,
  132 + EditFeeCombo,
  133 + DeleteFeeCombo
  134 + },
  135 + data() {
  136 + return {
  137 + loading: false,
  138 + searchForm: {
  139 + comboName: ''
  140 + },
  141 + tableData: [],
  142 + page: {
  143 + current: 1,
  144 + size: 10,
  145 + total: 0
  146 + },
  147 + communityId: ''
  148 + }
  149 + },
  150 + created() {
  151 + this.communityId = getCommunityId()
  152 + this.getList()
  153 + },
  154 + methods: {
  155 + async getList() {
  156 + try {
  157 + this.loading = true
  158 + const params = {
  159 + page: this.page.current,
  160 + row: this.page.size,
  161 + comboName: this.searchForm.comboName,
  162 + communityId: this.communityId
  163 + }
  164 + const { data, total } = await listFeeCombo(params)
  165 + this.tableData = data
  166 + this.page.total = total
  167 + } catch (error) {
  168 + this.$message.error(this.$t('feeComboManage.fetchError'))
  169 + } finally {
  170 + this.loading = false
  171 + }
  172 + },
  173 + handleSearch() {
  174 + this.page.current = 1
  175 + this.getList()
  176 + },
  177 + handleReset() {
  178 + this.searchForm = {
  179 + comboName: ''
  180 + }
  181 + this.getList()
  182 + },
  183 + handleAdd() {
  184 + this.$refs.addFeeCombo.open()
  185 + },
  186 + handleEdit(row) {
  187 + this.$refs.editFeeCombo.open(row)
  188 + },
  189 + handleDelete(row) {
  190 + this.$refs.deleteFeeCombo.open(row)
  191 + },
  192 + handleToComboMember(row) {
  193 + this.$router.push({
  194 + path: '/views/fee/feeComboMemberManage',
  195 + query: {
  196 + comboId: row.comboId,
  197 + comboName: row.comboName
  198 + }
  199 + })
  200 + },
  201 + handleSuccess() {
  202 + this.getList()
  203 + },
  204 + handleSizeChange(val) {
  205 + this.page.size = val
  206 + this.getList()
  207 + },
  208 + handleCurrentChange(val) {
  209 + this.page.current = val
  210 + this.getList()
  211 + }
  212 + }
  213 +}
  214 +</script>
  215 +
  216 +<style lang="scss" scoped>
  217 +.fee-combo-manage-container {
  218 + padding: 20px;
  219 +
  220 + .search-wrapper {
  221 + margin-bottom: 20px;
  222 +
  223 + .el-input {
  224 + width: 100%;
  225 + }
  226 + }
  227 +
  228 + .list-wrapper {
  229 + margin-bottom: 20px;
  230 +
  231 + .remark-wrapper {
  232 + margin-top: 20px;
  233 + color: #999;
  234 + font-size: 14px;
  235 + }
  236 + }
  237 +
  238 + .el-pagination {
  239 + margin-top: 20px;
  240 + text-align: right;
  241 + }
  242 +}
  243 +</style>
0 244 \ No newline at end of file
... ...
src/views/fee/feeComboMemberManageLang.js 0 → 100644
  1 +export const messages = {
  2 + en: {
  3 + feeComboMemberManage: {
  4 + title: 'Fee Items',
  5 + feeType: 'Fee Type',
  6 + feeItem: 'Fee Item',
  7 + feeFlag: 'Fee Flag',
  8 + paymentType: 'Payment Type',
  9 + paymentCycle: 'Payment Cycle',
  10 + unitPrice: 'Unit Price',
  11 + additionalFee: 'Additional/Fixed Fee',
  12 + prepay: 'Prepay',
  13 + postpay: 'Postpay',
  14 + back: 'Back',
  15 + selectFeeType: 'Please select fee type',
  16 + selectFeeItem: 'Please select fee item',
  17 + feeTypeRequired: 'Fee type is required',
  18 + feeItemRequired: 'Fee item is required',
  19 + confirmDelete: 'Confirm to delete this fee combo member?',
  20 + addSuccess: 'Add fee combo member successfully',
  21 + deleteSuccess: 'Delete fee combo member successfully',
  22 + fetchError: 'Failed to fetch fee combo members',
  23 + fetchDictError: 'Failed to fetch dictionary data',
  24 + fetchFeeConfigError: 'Failed to fetch fee configs',
  25 + addFailed: 'Failed to add fee combo member',
  26 + deleteFailed: 'Failed to delete fee combo member'
  27 + }
  28 + },
  29 + zh: {
  30 + feeComboMemberManage: {
  31 + title: '费用项',
  32 + feeType: '费用类型',
  33 + feeItem: '收费项目',
  34 + feeFlag: '费用标识',
  35 + paymentType: '付费类型',
  36 + paymentCycle: '缴费周期',
  37 + unitPrice: '计费单价',
  38 + additionalFee: '附加/固定费',
  39 + prepay: '预付费',
  40 + postpay: '后付费',
  41 + back: '返回',
  42 + selectFeeType: '请选择费用类型',
  43 + selectFeeItem: '请选择收费项目',
  44 + feeTypeRequired: '费用类型不能为空',
  45 + feeItemRequired: '收费项目不能为空',
  46 + confirmDelete: '确定删除该费用套餐成员?',
  47 + addSuccess: '添加费用套餐成员成功',
  48 + deleteSuccess: '删除费用套餐成员成功',
  49 + fetchError: '获取费用套餐成员失败',
  50 + fetchDictError: '获取字典数据失败',
  51 + fetchFeeConfigError: '获取费用配置失败',
  52 + addFailed: '添加费用套餐成员失败',
  53 + deleteFailed: '删除费用套餐成员失败'
  54 + }
  55 + }
  56 +}
0 57 \ No newline at end of file
... ...
src/views/fee/feeComboMemberManageList.vue 0 → 100644
  1 +<template>
  2 + <div class="fee-combo-member-manage-container">
  3 + <el-card class="box-card">
  4 + <div slot="header" class="clearfix">
  5 + <span>{{ feeComboMemberManageInfo.comboName }} {{ $t('feeComboMemberManage.title') }}</span>
  6 + <div class="header-tools">
  7 + <el-button type="primary" size="small" @click="_openAddFeeComboMemberModal">
  8 + <i class="el-icon-plus"></i>
  9 + {{ $t('common.add') }}
  10 + </el-button>
  11 + <el-button type="primary" size="small" @click="_goBack">
  12 + {{ $t('feeComboMemberManage.back') }}
  13 + </el-button>
  14 + </div>
  15 + </div>
  16 +
  17 + <el-table
  18 + :data="feeComboMemberManageInfo.feeComboMembers"
  19 + border
  20 + style="width: 100%"
  21 + v-loading="loading"
  22 + >
  23 + <el-table-column
  24 + prop="feeTypeCdName"
  25 + :label="$t('feeComboMemberManage.feeType')"
  26 + align="center"
  27 + />
  28 + <el-table-column
  29 + prop="feeName"
  30 + :label="$t('feeComboMemberManage.feeItem')"
  31 + align="center"
  32 + />
  33 + <el-table-column
  34 + prop="feeFlagName"
  35 + :label="$t('feeComboMemberManage.feeFlag')"
  36 + align="center"
  37 + />
  38 + <el-table-column
  39 + :label="$t('feeComboMemberManage.paymentType')"
  40 + align="center"
  41 + >
  42 + <template slot-scope="scope">
  43 + {{ scope.row.paymentCd == '1200' ? $t('feeComboMemberManage.prepay') : $t('feeComboMemberManage.postpay') }}
  44 + </template>
  45 + </el-table-column>
  46 + <el-table-column
  47 + prop="paymentCycle"
  48 + :label="$t('feeComboMemberManage.paymentCycle')"
  49 + align="center"
  50 + />
  51 + <el-table-column
  52 + :label="$t('feeComboMemberManage.unitPrice')"
  53 + align="center"
  54 + >
  55 + <template slot-scope="scope">
  56 + {{ scope.row.computingFormula == '2002' ? '-' : scope.row.squarePrice }}
  57 + </template>
  58 + </el-table-column>
  59 + <el-table-column
  60 + prop="additionalAmount"
  61 + :label="$t('feeComboMemberManage.additionalFee')"
  62 + align="center"
  63 + />
  64 + <el-table-column
  65 + :label="$t('common.operation')"
  66 + align="center"
  67 + width="150"
  68 + >
  69 + <template slot-scope="scope">
  70 + <el-button
  71 + type="danger"
  72 + size="mini"
  73 + @click="_openDeleteFeeComboMemberModel(scope.row)"
  74 + >
  75 + {{ $t('common.delete') }}
  76 + </el-button>
  77 + </template>
  78 + </el-table-column>
  79 + </el-table>
  80 +
  81 + <el-pagination
  82 + @size-change="handleSizeChange"
  83 + @current-change="handleCurrentChange"
  84 + :current-page="page.current"
  85 + :page-sizes="[10, 20, 30, 50]"
  86 + :page-size="page.size"
  87 + layout="total, sizes, prev, pager, next, jumper"
  88 + :total="page.total"
  89 + class="pagination"
  90 + />
  91 + </el-card>
  92 +
  93 + <add-fee-combo-member ref="addFeeComboMember" @success="handleSuccess" />
  94 + <delete-fee-combo-member ref="deleteFeeComboMember" @success="handleSuccess" />
  95 + </div>
  96 +</template>
  97 +
  98 +<script>
  99 +import { listFeeComboMember } from '@/api/fee/feeComboMemberManageApi'
  100 +import AddFeeComboMember from '@/components/fee/addFeeComboMember'
  101 +import DeleteFeeComboMember from '@/components/fee/deleteFeeComboMember'
  102 +import { getCommunityId } from '@/api/community/communityApi'
  103 +
  104 +export default {
  105 + name: 'FeeComboMemberManageList',
  106 + components: {
  107 + AddFeeComboMember,
  108 + DeleteFeeComboMember
  109 + },
  110 + data() {
  111 + return {
  112 + loading: false,
  113 + feeComboMemberManageInfo: {
  114 + feeComboMembers: [],
  115 + comboId: '',
  116 + comboName: '',
  117 + conditions: {
  118 + comboId: '',
  119 + comboName: '',
  120 + communityId: ''
  121 + }
  122 + },
  123 + page: {
  124 + current: 1,
  125 + size: 10,
  126 + total: 0
  127 + }
  128 + }
  129 + },
  130 + created() {
  131 + this.feeComboMemberManageInfo.conditions.comboId = this.$route.query.comboId
  132 + this.feeComboMemberManageInfo.comboName = this.$route.query.comboName
  133 + this.feeComboMemberManageInfo.conditions.communityId = getCommunityId()
  134 + this._listFeeComboMembers()
  135 + },
  136 + methods: {
  137 + async _listFeeComboMembers() {
  138 + try {
  139 + this.loading = true
  140 + const params = {
  141 + page: this.page.current,
  142 + row: this.page.size,
  143 + ...this.feeComboMemberManageInfo.conditions
  144 + }
  145 + const { data, total } = await listFeeComboMember(params)
  146 + this.feeComboMemberManageInfo.feeComboMembers = data
  147 + this.page.total = total
  148 + } catch (error) {
  149 + this.$message.error(this.$t('feeComboMemberManage.fetchError'))
  150 + } finally {
  151 + this.loading = false
  152 + }
  153 + },
  154 + _openAddFeeComboMemberModal() {
  155 + this.$refs.addFeeComboMember.open({
  156 + comboId: this.feeComboMemberManageInfo.conditions.comboId
  157 + })
  158 + },
  159 + _openDeleteFeeComboMemberModel(feeComboMember) {
  160 + this.$refs.deleteFeeComboMember.open(feeComboMember)
  161 + },
  162 + _goBack() {
  163 + this.$router.go(-1)
  164 + },
  165 + handleSuccess() {
  166 + this._listFeeComboMembers()
  167 + },
  168 + handleSizeChange(val) {
  169 + this.page.size = val
  170 + this._listFeeComboMembers()
  171 + },
  172 + handleCurrentChange(val) {
  173 + this.page.current = val
  174 + this._listFeeComboMembers()
  175 + }
  176 + }
  177 +}
  178 +</script>
  179 +
  180 +<style lang="scss" scoped>
  181 +.fee-combo-member-manage-container {
  182 + padding: 20px;
  183 +
  184 + .clearfix {
  185 + display: flex;
  186 + justify-content: space-between;
  187 + align-items: center;
  188 + }
  189 +
  190 + .header-tools {
  191 + display: flex;
  192 + gap: 10px;
  193 + }
  194 +
  195 + .pagination {
  196 + margin-top: 20px;
  197 + text-align: right;
  198 + }
  199 +}
  200 +</style>
0 201 \ No newline at end of file
... ...
src/views/fee/payFeeAuditManageLang.js 0 → 100644
  1 +export const messages = {
  2 + en: {
  3 + payFeeAuditManage: {
  4 + searchTitle: 'Search Conditions',
  5 + tableTitle: 'Payment Audit',
  6 + selectPayObj: 'Select Payment Object',
  7 + selectStatus: 'Select Status',
  8 + inputPlateNumber: 'Enter License Plate Number',
  9 + inputRoomInfo: 'Enter Building-Unit-Room',
  10 + house: 'House',
  11 + feeItem: 'Fee Item',
  12 + payCycle: 'Payment Cycle',
  13 + month: 'month(s)',
  14 + startTime: 'Start Time',
  15 + endTime: 'End Time',
  16 + receivableAmount: 'Receivable Amount(Yuan)',
  17 + receivedAmount: 'Received Amount(Yuan)',
  18 + operator: 'Operator',
  19 + payTime: 'Payment Time',
  20 + auditStatus: 'Audit Status',
  21 + auditRemark: 'Audit Remark',
  22 + payRemark: 'Payment Remark',
  23 + pendingReview: 'Pending Review',
  24 + approved: 'Approved',
  25 + rejected: 'Rejected',
  26 + batchAudit: 'Batch Audit',
  27 + auditFee: 'Audit Fee',
  28 + refund: 'Refund',
  29 + auditTip: 'Payment audit will not affect payment accounting, used by finance to verify payment correctness',
  30 + fetchError: 'Failed to fetch payment audit data',
  31 + selectFeeTip: 'Please select at least one fee'
  32 + },
  33 + audit: {
  34 + auditInfo: 'Audit Information',
  35 + auditStatus: 'Audit Status',
  36 + selectAudit: 'Select Audit',
  37 + agree: 'Agree',
  38 + reject: 'Reject',
  39 + reason: 'Reason',
  40 + inputReason: 'Required, please enter reason',
  41 + stateRequired: 'Audit status is required',
  42 + reasonRequired: 'Reason is required',
  43 + reasonMaxLength: 'Reason cannot exceed 200 characters'
  44 + },
  45 + returnPayFee: {
  46 + refundTitle: 'Fill Refund Reason',
  47 + fee: 'Payment',
  48 + cycle: 'Cycle',
  49 + month: 'month',
  50 + receivableAmount: 'Receivable Amount',
  51 + receivedAmount: 'Received Amount',
  52 + payTime: 'Payment Time',
  53 + refundReason: 'Refund Reason',
  54 + inputReason: 'Enter refund reason',
  55 + submitRefund: 'Submit Refund Application',
  56 + reasonRequired: 'Refund reason is required',
  57 + reasonLength: 'Refund reason must be between 1-200 characters'
  58 + }
  59 + },
  60 + zh: {
  61 + payFeeAuditManage: {
  62 + searchTitle: '查询条件',
  63 + tableTitle: '缴费审核',
  64 + selectPayObj: '请选择付费对象',
  65 + selectStatus: '请选择状态',
  66 + inputPlateNumber: '请填写车牌号',
  67 + inputRoomInfo: '请填写房屋 楼栋-单元-室',
  68 + house: '房屋',
  69 + feeItem: '费用项目',
  70 + payCycle: '付费周期',
  71 + month: '个月',
  72 + startTime: '缴费起始时间',
  73 + endTime: '缴费结束时间',
  74 + receivableAmount: '应付金额(单位:元)',
  75 + receivedAmount: '实付金额(单位:元)',
  76 + operator: '操作员工',
  77 + payTime: '缴费时间',
  78 + auditStatus: '审核状态',
  79 + auditRemark: '审核说明',
  80 + payRemark: '缴费备注',
  81 + pendingReview: '待审核',
  82 + approved: '审核通过',
  83 + rejected: '审核未通过',
  84 + batchAudit: '批量审核',
  85 + auditFee: '审核费用',
  86 + refund: '退费',
  87 + auditTip: '缴费审核不会影响缴费入账,财务用来核对缴费是否正确',
  88 + fetchError: '获取缴费审核数据失败',
  89 + selectFeeTip: '请选择费用'
  90 + },
  91 + audit: {
  92 + auditInfo: '审核信息',
  93 + auditStatus: '审核状态',
  94 + selectAudit: '请审核',
  95 + agree: '同意',
  96 + reject: '拒绝',
  97 + reason: '原因',
  98 + inputReason: '必填,请填写原因',
  99 + stateRequired: '审核状态不能为空',
  100 + reasonRequired: '原因内容不能为空',
  101 + reasonMaxLength: '原因内容不能超过200'
  102 + },
  103 + returnPayFee: {
  104 + refundTitle: '填写退费原因',
  105 + fee: '缴费',
  106 + cycle: '周期',
  107 + month: '月',
  108 + receivableAmount: '应收金额',
  109 + receivedAmount: '实收金额',
  110 + payTime: '缴费时间',
  111 + refundReason: '退费原因',
  112 + inputReason: '填写退费原因',
  113 + submitRefund: '提交退费申请',
  114 + reasonRequired: '退费原因不能为空',
  115 + reasonLength: '退费原因必须为1-200个字符'
  116 + }
  117 + }
  118 +}
0 119 \ No newline at end of file
... ...
src/views/fee/payFeeAuditManageList.vue 0 → 100644
  1 +<template>
  2 + <div class="pay-fee-audit-manage-container">
  3 + <el-card class="search-card">
  4 + <div slot="header" class="flex justify-between">
  5 + <span>{{ $t('payFeeAuditManage.searchTitle') }}</span>
  6 + </div>
  7 + <el-row :gutter="20">
  8 + <el-col :span="6">
  9 + <el-select
  10 + v-model="searchForm.payObjType"
  11 + :placeholder="$t('payFeeAuditManage.selectPayObj')"
  12 + style="width:100%"
  13 + >
  14 + <el-option
  15 + v-for="item in payObjTypes"
  16 + :key="item.statusCd"
  17 + :label="item.name"
  18 + :value="item.statusCd"
  19 + />
  20 + </el-select>
  21 + </el-col>
  22 + <el-col :span="6">
  23 + <el-select
  24 + v-model="searchForm.state"
  25 + :placeholder="$t('payFeeAuditManage.selectStatus')"
  26 + style="width:100%"
  27 + >
  28 + <el-option
  29 + :label="$t('payFeeAuditManage.pendingReview')"
  30 + value="1010"
  31 + />
  32 + <el-option
  33 + :label="$t('payFeeAuditManage.approved')"
  34 + value="2020"
  35 + />
  36 + <el-option
  37 + :label="$t('payFeeAuditManage.rejected')"
  38 + value="3030"
  39 + />
  40 + </el-select>
  41 + </el-col>
  42 + <el-col :span="6">
  43 + <el-input
  44 + v-model="searchForm.payerObjId"
  45 + :placeholder="searchForm.payObjType === '666'
  46 + ? $t('payFeeAuditManage.inputPlateNumber')
  47 + : $t('payFeeAuditManage.inputRoomInfo')"
  48 + />
  49 + </el-col>
  50 + <el-col :span="6">
  51 + <el-button type="primary" @click="handleSearch">
  52 + <i class="el-icon-search"></i>
  53 + {{ $t('common.search') }}
  54 + </el-button>
  55 + <el-button @click="handleReset">
  56 + <i class="el-icon-refresh"></i>
  57 + {{ $t('common.reset') }}
  58 + </el-button>
  59 + </el-col>
  60 + </el-row>
  61 + </el-card>
  62 +
  63 + <el-card class="table-card">
  64 + <div slot="header" class="flex justify-between">
  65 + <span>{{ $t('payFeeAuditManage.tableTitle') }}</span>
  66 + <el-button
  67 + type="primary"
  68 + size="small"
  69 + style="float:right"
  70 + @click="handleBatchAudit"
  71 + >
  72 + <i class="el-icon-plus"></i>
  73 + {{ $t('payFeeAuditManage.batchAudit') }}
  74 + </el-button>
  75 + </div>
  76 +
  77 + <el-table
  78 + :data="tableData"
  79 + border
  80 + style="width:100%"
  81 + @selection-change="handleSelectionChange"
  82 + >
  83 + <el-table-column type="selection" width="55" align="center" />
  84 + <el-table-column
  85 + prop="payerObjName"
  86 + :label="$t('payFeeAuditManage.house')"
  87 + align="center"
  88 + />
  89 + <el-table-column
  90 + prop="feeName"
  91 + :label="$t('payFeeAuditManage.feeItem')"
  92 + align="center"
  93 + />
  94 + <el-table-column
  95 + prop="cycles"
  96 + :label="$t('payFeeAuditManage.payCycle')"
  97 + align="center"
  98 + >
  99 + <template slot-scope="scope">
  100 + {{ scope.row.cycles }} {{ $t('payFeeAuditManage.month') }}
  101 + </template>
  102 + </el-table-column>
  103 + <el-table-column
  104 + prop="startTime"
  105 + :label="$t('payFeeAuditManage.startTime')"
  106 + align="center"
  107 + />
  108 + <el-table-column
  109 + prop="endTime"
  110 + :label="$t('payFeeAuditManage.endTime')"
  111 + align="center"
  112 + />
  113 + <el-table-column
  114 + prop="receivableAmount"
  115 + :label="$t('payFeeAuditManage.receivableAmount')"
  116 + align="center"
  117 + />
  118 + <el-table-column
  119 + prop="receivedAmount"
  120 + :label="$t('payFeeAuditManage.receivedAmount')"
  121 + align="center"
  122 + />
  123 + <el-table-column
  124 + prop="userName"
  125 + :label="$t('payFeeAuditManage.operator')"
  126 + align="center"
  127 + />
  128 + <el-table-column
  129 + prop="createTime"
  130 + :label="$t('payFeeAuditManage.payTime')"
  131 + align="center"
  132 + />
  133 + <el-table-column
  134 + prop="state"
  135 + :label="$t('payFeeAuditManage.auditStatus')"
  136 + align="center"
  137 + >
  138 + <template slot-scope="scope">
  139 + <el-tag
  140 + :type="getStatusTagType(scope.row.state)"
  141 + >
  142 + {{ getStatusText(scope.row.state) }}
  143 + </el-tag>
  144 + </template>
  145 + </el-table-column>
  146 + <el-table-column
  147 + prop="message"
  148 + :label="$t('payFeeAuditManage.auditRemark')"
  149 + align="center"
  150 + />
  151 + <el-table-column
  152 + prop="remark"
  153 + :label="$t('payFeeAuditManage.payRemark')"
  154 + align="center"
  155 + />
  156 + <el-table-column
  157 + :label="$t('common.operation')"
  158 + align="center"
  159 + width="200"
  160 + >
  161 + <template slot-scope="scope">
  162 + <el-button
  163 + type="text"
  164 + size="small"
  165 + @click="handleDetail(scope.row)"
  166 + >
  167 + {{ $t('common.detail') }}
  168 + </el-button>
  169 + <el-button
  170 + v-if="scope.row.state !== '2020' && scope.row.state !== '3030'"
  171 + type="text"
  172 + size="small"
  173 + @click="handleAudit(scope.row)"
  174 + >
  175 + {{ $t('payFeeAuditManage.auditFee') }}
  176 + </el-button>
  177 + <el-button
  178 + v-if="scope.row.state === '3030'"
  179 + type="text"
  180 + size="small"
  181 + @click="handleRefund(scope.row)"
  182 + >
  183 + {{ $t('payFeeAuditManage.refund') }}
  184 + </el-button>
  185 + </template>
  186 + </el-table-column>
  187 + </el-table>
  188 +
  189 + <div class="pagination-wrapper">
  190 + <div class="tip-text">
  191 + {{ $t('payFeeAuditManage.auditTip') }}
  192 + </div>
  193 + <el-pagination
  194 + :current-page="pagination.current"
  195 + :page-sizes="[10, 20, 30, 50]"
  196 + :page-size="pagination.size"
  197 + :total="pagination.total"
  198 + layout="total, sizes, prev, pager, next, jumper"
  199 + @size-change="handleSizeChange"
  200 + @current-change="handleCurrentChange"
  201 + />
  202 + </div>
  203 + </el-card>
  204 +
  205 + <audit-modal ref="auditModal" @success="handleAuditSuccess" />
  206 + <return-pay-fee-modal ref="returnPayFeeModal" @success="handleRefundSuccess" />
  207 + </div>
  208 +</template>
  209 +
  210 +<script>
  211 +import { getPayFeeAuditList, savePayFeeAudit } from '@/api/fee/payFeeAuditManageApi'
  212 +import { getDict } from '@/api/community/communityApi'
  213 +import { getCommunityId } from '@/api/community/communityApi'
  214 +import AuditModal from '@/components/fee/audit'
  215 +import ReturnPayFeeModal from '@/components/fee/returnPayFee'
  216 +
  217 +export default {
  218 + name: 'PayFeeAuditManageList',
  219 + components: {
  220 + AuditModal,
  221 + ReturnPayFeeModal
  222 + },
  223 + data() {
  224 + return {
  225 + searchForm: {
  226 + communityId: '',
  227 + payObjType: '',
  228 + state: '1010',
  229 + payerObjId: ''
  230 + },
  231 + tableData: [],
  232 + multipleSelection: [],
  233 + payObjTypes: [],
  234 + pagination: {
  235 + current: 1,
  236 + size: 10,
  237 + total: 0
  238 + },
  239 + currentRow: null
  240 + }
  241 + },
  242 + created() {
  243 + this.searchForm.communityId = getCommunityId()
  244 + this.getList()
  245 + this.getPayObjTypes()
  246 + },
  247 + methods: {
  248 + async getList() {
  249 + try {
  250 + const params = {
  251 + ...this.searchForm,
  252 + page: this.pagination.current,
  253 + row: this.pagination.size
  254 + }
  255 + const { data, total } = await getPayFeeAuditList(params)
  256 + this.tableData = data
  257 + this.pagination.total = total
  258 + } catch (error) {
  259 + this.$message.error(this.$t('payFeeAuditManage.fetchError'))
  260 + }
  261 + },
  262 + async getPayObjTypes() {
  263 + try {
  264 + this.payObjTypes = await getDict('pay_fee', 'payer_obj_type')
  265 + } catch (error) {
  266 + console.error('获取付费对象类型失败:', error)
  267 + }
  268 + },
  269 + handleSearch() {
  270 + this.pagination.current = 1
  271 + this.getList()
  272 + },
  273 + handleReset() {
  274 + this.searchForm = {
  275 + communityId: getCommunityId(),
  276 + payObjType: '',
  277 + state: '1010',
  278 + payerObjId: ''
  279 + }
  280 + this.handleSearch()
  281 + },
  282 + handleSizeChange(val) {
  283 + this.pagination.size = val
  284 + this.getList()
  285 + },
  286 + handleCurrentChange(val) {
  287 + this.pagination.current = val
  288 + this.getList()
  289 + },
  290 + handleSelectionChange(val) {
  291 + this.multipleSelection = val
  292 + },
  293 + handleDetail(row) {
  294 + this.$router.push({
  295 + path: '/fee/propertyFee',
  296 + query: row
  297 + })
  298 + },
  299 + handleAudit(row) {
  300 + this.currentRow = row
  301 + this.$refs.auditModal.open()
  302 + },
  303 + handleBatchAudit() {
  304 + if (this.multipleSelection.length === 0) {
  305 + this.$message.warning(this.$t('payFeeAuditManage.selectFeeTip'))
  306 + return
  307 + }
  308 + this.currentRow = null
  309 + this.$refs.auditModal.open()
  310 + },
  311 + handleRefund(row) {
  312 + this.$refs.returnPayFeeModal.open(row)
  313 + },
  314 + async handleAuditSuccess(auditInfo) {
  315 + try {
  316 + const params = {
  317 + state: auditInfo.state === '1100' ? '2020' : '3030',
  318 + message: auditInfo.remark,
  319 + feeDetailId: this.currentRow
  320 + ? this.currentRow.detailId
  321 + : this.multipleSelection.map(item => item.detailId).join(','),
  322 + communityId: this.searchForm.communityId
  323 + }
  324 + await savePayFeeAudit(params)
  325 + this.$message.success(this.$t('common.operateSuccess'))
  326 + this.getList()
  327 + } catch (error) {
  328 + this.$message.error(error.message || this.$t('common.operateFailed'))
  329 + }
  330 + },
  331 + handleRefundSuccess() {
  332 + this.getList()
  333 + },
  334 + getStatusTagType(state) {
  335 + switch(state) {
  336 + case '2020': return 'success'
  337 + case '3030': return 'danger'
  338 + default: return 'info'
  339 + }
  340 + },
  341 + getStatusText(state) {
  342 + switch(state) {
  343 + case '2020': return this.$t('payFeeAuditManage.approved')
  344 + case '3030': return this.$t('payFeeAuditManage.rejected')
  345 + default: return this.$t('payFeeAuditManage.pendingReview')
  346 + }
  347 + }
  348 + }
  349 +}
  350 +</script>
  351 +
  352 +<style lang="scss" scoped>
  353 +.pay-fee-audit-manage-container {
  354 + padding: 20px;
  355 +
  356 + .search-card {
  357 + margin-bottom: 20px;
  358 + }
  359 +
  360 + .pagination-wrapper {
  361 + display: flex;
  362 + justify-content: space-between;
  363 + align-items: center;
  364 + margin-top: 20px;
  365 +
  366 + .tip-text {
  367 + color: #999;
  368 + font-size: 12px;
  369 + }
  370 + }
  371 +}
  372 +</style>
0 373 \ No newline at end of file
... ...