| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699 |
- <template>
- <div class="limit-purchase-rule-container">
- <!-- 规则列表区域 -->
- <el-card class="rule-list-card">
- <div slot="header" class="card-header">
- <span>限购规则列表</span>
- <el-button type="primary" size="small" @click="handleCreateRule">
- 新建规则
- </el-button>
- </div>
- <el-table
- v-loading="loading.list"
- :data="ruleList"
- border
- style="width: 100%"
- >
- <el-table-column prop="id" label="ID" width="80" />
- <el-table-column prop="name" label="规则名称" min-width="160" />
- <el-table-column label="限购地区" min-width="260">
- <template slot-scope="scope">
- <div v-if="scope.row.regions && scope.row.regions.length">
- <el-tag
- v-for="(region, index) in scope.row.regions"
- :key="index"
- type="danger"
- size="mini"
- class="region-tag"
- >
- {{ formatRegionLabel(region) }}
- </el-tag>
- </div>
- <span v-else class="text-muted">未设置</span>
- </template>
- </el-table-column>
- <el-table-column prop="status" label="状态" width="100">
- <template slot-scope="scope">
- <el-tag :type="scope.row.status === 1 ? 'success' : 'info'" size="small">
- {{ scope.row.status === 1 ? '启用' : '停用' }}
- </el-tag>
- </template>
- </el-table-column>
- <el-table-column label="操作" width="200" fixed="right">
- <template slot-scope="scope">
- <el-button type="text" size="small" @click="handleEditRule(scope.row)">
- 编辑
- </el-button>
- <el-button type="text" size="small" @click="handleEditRegions(scope.row)">
- 设置限购地区
- </el-button>
- <el-button type="text" size="small" class="danger-text" @click="handleDeleteRule(scope.row)">
- 删除
- </el-button>
- </template>
- </el-table-column>
- </el-table>
- </el-card>
- <!-- 规则表单弹窗:新建 / 编辑规则基本信息 -->
- <el-dialog
- :title="ruleForm.id ? '编辑限购规则' : '新建限购规则'"
- :visible.sync="ruleDialog.visible"
- width="720px"
- :close-on-click-modal="false"
- >
- <el-form
- ref="ruleFormRef"
- :model="ruleForm"
- :rules="ruleRules"
- label-width="90px"
- >
- <el-form-item label="规则名称" prop="name">
- <el-input v-model="ruleForm.name" maxlength="64" show-word-limit />
- </el-form-item>
- <el-form-item label="状态" prop="status">
- <el-switch
- v-model="ruleForm.status"
- :active-value="1"
- :inactive-value="0"
- />
- </el-form-item>
- <!-- 在新建 / 编辑规则里直接设置限购地区(本地省市区 JSON 级联) -->
- <el-form-item label="限购地区">
- <div class="region-selector-wrapper">
- <el-form label-width="60px" class="region-form inner-region-form">
- <el-row :gutter="20" type="flex" align="middle">
- <el-col :span="6">
- <el-form-item label="省份">
- <el-select
- v-model="selectedProvince"
- placeholder="请选择省份"
- filterable
- clearable
- @change="onProvinceChange"
- >
- <el-option
- v-for="p in provinces"
- :key="p.name"
- :label="p.name"
- :value="p.name"
- />
- </el-select>
- </el-form-item>
- </el-col>
- <el-col :span="6">
- <el-form-item label="城市">
- <el-select
- v-model="selectedCity"
- placeholder="请选择城市"
- :disabled="!selectedProvince"
- filterable
- clearable
- @change="onCityChange"
- >
- <el-option
- v-for="c in cities"
- :key="c.name"
- :label="c.name"
- :value="c.name"
- />
- </el-select>
- </el-form-item>
- </el-col>
- <el-col :span="6">
- <el-form-item label="区县">
- <el-select
- v-model="selectedArea"
- placeholder="请选择区县"
- :disabled="!selectedCity"
- filterable
- clearable
- >
- <el-option
- v-for="a in areas"
- :key="a"
- :label="a"
- :value="a"
- />
- </el-select>
- </el-form-item>
- </el-col>
- <el-col :span="6" class="text-right">
- <el-button type="primary" @click="addRegion">
- <i class="el-icon-plus" /> 添加限购地区
- </el-button>
- </el-col>
- </el-row>
- </el-form>
- <div class="limited-region-list">
- <div class="list-title">已设置的限购地区</div>
- <div v-if="currentRuleRegions.length === 0" class="empty-tip">
- 暂未添加限购地区
- </div>
- <div
- v-for="(area, index) in currentRuleRegions"
- :key="index"
- class="limited-area-item"
- >
- <span>{{ formatRegionLabel(area) }}</span>
- <el-button
- type="text"
- class="danger-text"
- icon="el-icon-delete"
- @click="removeRegion(index)"
- >
- 删除
- </el-button>
- </div>
- </div>
- </div>
- </el-form-item>
- </el-form>
- <span slot="footer" class="dialog-footer">
- <el-button @click="ruleDialog.visible = false">取 消</el-button>
- <el-button type="primary" :loading="loading.saveRule" @click="submitRuleForm">
- 确 认
- </el-button>
- </span>
- </el-dialog>
- <!-- 地区设置弹窗:管理单条规则下的多个限购地区 -->
- <el-dialog
- title="设置限购地区"
- :visible.sync="regionDialog.visible"
- width="720px"
- :close-on-click-modal="false"
- >
- <div class="region-selector-wrapper">
- <div class="region-selector-title">
- <i class="el-icon-warning-outline text-danger" />
- <span> 设置限购地区</span>
- </div>
- <el-form label-width="60px" class="region-form">
- <el-row :gutter="20" type="flex" align="middle">
- <el-col :span="6">
- <el-form-item label="省份">
- <el-select
- v-model="selectedProvince"
- placeholder="请选择省份"
- filterable
- clearable
- @change="onProvinceChange"
- >
- <el-option
- v-for="p in provinces"
- :key="p.name"
- :label="p.name"
- :value="p.name"
- />
- </el-select>
- </el-form-item>
- </el-col>
- <el-col :span="6">
- <el-form-item label="城市">
- <el-select
- v-model="selectedCity"
- placeholder="请选择城市"
- :disabled="!selectedProvince"
- filterable
- clearable
- @change="onCityChange"
- >
- <el-option
- v-for="c in cities"
- :key="c.name"
- :label="c.name"
- :value="c.name"
- />
- </el-select>
- </el-form-item>
- </el-col>
- <el-col :span="6">
- <el-form-item label="区县">
- <el-select
- v-model="selectedArea"
- placeholder="请选择区县"
- :disabled="!selectedCity"
- filterable
- clearable
- >
- <el-option
- v-for="a in areas"
- :key="a"
- :label="a"
- :value="a"
- />
- </el-select>
- </el-form-item>
- </el-col>
- <el-col :span="6" class="text-right">
- <el-button type="primary" @click="addRegion">
- <i class="el-icon-plus" /> 添加限购地区
- </el-button>
- </el-col>
- </el-row>
- </el-form>
- <div class="limited-region-list">
- <div class="list-title">已设置的限购地区</div>
- <div v-if="currentRuleRegions.length === 0" class="empty-tip">
- 暂未添加限购地区
- </div>
- <div
- v-for="(area, index) in currentRuleRegions"
- :key="index"
- class="limited-area-item"
- >
- <span>{{ formatRegionLabel(area) }}</span>
- <el-button
- type="text"
- class="danger-text"
- icon="el-icon-delete"
- @click="removeRegion(index)"
- >
- 删除
- </el-button>
- </div>
- </div>
- </div>
- <span slot="footer" class="dialog-footer">
- <el-button @click="regionDialog.visible = false">取 消</el-button>
- <el-button type="primary" :loading="loading.saveRegions" @click="submitRegions">
- 确 认 设置
- </el-button>
- </span>
- </el-dialog>
- </div>
- </template>
- <script>
- // 前端内置省市区 JSON,不走接口
- // 这里只演示北京和天津两组数据,后续可按同样格式扩展
- const REGION_TREE = [
- {
- name: '北京市',
- city: [
- {
- name: '北京市',
- area: [
- '东城区',
- '西城区',
- '崇文区',
- '宣武区',
- '朝阳区',
- '丰台区',
- '石景山区',
- '海淀区',
- '门头沟区',
- '房山区',
- '通州区',
- '顺义区',
- '昌平区',
- '大兴区',
- '平谷区',
- '怀柔区',
- '密云县',
- '延庆县'
- ]
- }
- ]
- },
- {
- name: '天津市',
- city: [
- {
- name: '天津市',
- area: [
- '和平区',
- '河东区',
- '河西区',
- '南开区',
- '河北区',
- '红桥区',
- '塘沽区',
- '汉沽区',
- '大港区',
- '东丽区',
- '西青区',
- '津南区',
- '北辰区',
- '武清区',
- '宝坻区',
- '宁河县',
- '静海县',
- '蓟县'
- ]
- }
- ]
- }
- ]
- export default {
- name: 'LimitPurchaseRule',
- props: {
- axiosInstance: {
- type: Object,
- default: null
- }
- },
- data() {
- return {
- // 列表数据
- ruleList: [],
- loading: {
- list: false,
- saveRule: false,
- saveRegions: false
- },
- // 规则表单
- ruleDialog: {
- visible: false
- },
- ruleForm: {
- id: null,
- name: '',
- status: 1
- },
- ruleRules: {
- name: [
- { required: true, message: '请输入规则名称', trigger: 'blur' }
- ]
- },
- // 地区相关
- regionDialog: {
- visible: false,
- ruleId: null
- },
- // 使用本地省市区 JSON 级联
- provinces: REGION_TREE,
- cities: [],
- areas: [],
- selectedProvince: '',
- selectedCity: '',
- selectedArea: '',
- currentRuleRegions: [] // 当前规则下的地区数组
- }
- },
- created() {
- this.fetchRuleList()
- },
- methods: {
- http() {
- // 默认用全局 axios,如果父组件传入则优先用父组件的
- return this.axiosInstance || this.$axios || this.$http
- },
- // ========== 列表相关 ==========
- async fetchRuleList() {
- if (!this.http()) return
- this.loading.list = true
- try {
- const { data } = await this.http().get('/limit_purchase/rule')
- // 按项目通用返回结构处理
- this.ruleList = Array.isArray(data?.data) ? data.data : (data || [])
- } catch (e) {
- console.error(e)
- this.$message.error('加载规则列表失败')
- } finally {
- this.loading.list = false
- }
- },
- handleCreateRule() {
- this.ruleForm = {
- id: null,
- name: '',
- status: 1
- }
- // 新建规则时清空当前规则地区
- this.currentRuleRegions = []
- this.selectedProvince = ''
- this.selectedCity = ''
- this.selectedArea = ''
- this.cities = []
- this.areas = []
- this.ruleDialog.visible = true
- this.$nextTick(() => {
- this.$refs.ruleFormRef && this.$refs.ruleFormRef.clearValidate()
- })
- },
- handleEditRule(row) {
- this.ruleForm = {
- id: row.id,
- name: row.name,
- status: row.status
- }
- // 编辑规则时加载该规则已有的地区
- this.currentRuleRegions = Array.isArray(row.regions) ? [...row.regions] : []
- this.selectedProvince = ''
- this.selectedCity = ''
- this.selectedArea = ''
- this.cities = []
- this.areas = []
- this.ruleDialog.visible = true
- this.$nextTick(() => {
- this.$refs.ruleFormRef && this.$refs.ruleFormRef.clearValidate()
- })
- },
- submitRuleForm() {
- this.$refs.ruleFormRef.validate(async valid => {
- if (!valid || !this.http()) return
- this.loading.saveRule = true
- try {
- if (this.ruleForm.id) {
- // 更新:名称 + 状态 + 限购地区
- await this.http().put(`/limit_purchase/rule/${this.ruleForm.id}`, {
- name: this.ruleForm.name,
- status: this.ruleForm.status,
- regions: this.currentRuleRegions
- })
- this.$message.success('更新成功')
- } else {
- // 创建:名称 + 状态 + 限购地区
- await this.http().post('/limit_purchase/rule', {
- name: this.ruleForm.name,
- status: this.ruleForm.status,
- regions: this.currentRuleRegions
- })
- this.$message.success('创建成功')
- }
- this.ruleDialog.visible = false
- this.fetchRuleList()
- } catch (e) {
- console.error(e)
- this.$message.error('保存失败')
- } finally {
- this.loading.saveRule = false
- }
- })
- },
- handleDeleteRule(row) {
- if (!this.http()) return
- this.$confirm(`确认删除规则「${row.name}」吗?`, '提示', {
- type: 'warning'
- })
- .then(async () => {
- try {
- await this.http().delete(`/limit_purchase/rule/${row.id}`)
- this.$message.success('删除成功')
- this.fetchRuleList()
- } catch (e) {
- console.error(e)
- this.$message.error('删除失败')
- }
- })
- .catch(() => {})
- },
- // ========== 地区设置相关 ==========
- handleEditRegions(row) {
- if (!row || !row.id) return
- this.regionDialog.ruleId = row.id
- this.selectedProvince = ''
- this.selectedCity = ''
- this.selectedArea = ''
- this.cities = []
- this.areas = []
- this.currentRuleRegions = Array.isArray(row.regions) ? [...row.regions] : []
- this.regionDialog.visible = true
- },
- onProvinceChange() {
- this.selectedCity = ''
- this.selectedArea = ''
- this.areas = []
- const province = REGION_TREE.find(p => p.name === this.selectedProvince)
- this.cities = province ? province.city : []
- },
- onCityChange() {
- this.selectedArea = ''
- const province = REGION_TREE.find(p => p.name === this.selectedProvince)
- const city = province ? province.city.find(c => c.name === this.selectedCity) : null
- this.areas = city ? city.area : []
- },
- addRegion() {
- if (!this.selectedProvince || !this.selectedCity || !this.selectedArea) {
- this.$message.warning('请选择省、市、区县')
- return
- }
- const region = {
- province_id: null,
- province_name: this.selectedProvince,
- city_id: null,
- city_name: this.selectedCity,
- district_id: null,
- district_name: this.selectedArea
- }
- // 去重:相同省市区不重复添加
- const exists = this.currentRuleRegions.some(item =>
- item.province_id === region.province_id &&
- item.city_id === region.city_id &&
- item.district_id === region.district_id
- )
- if (exists) {
- this.$message.warning('该地区已在列表中')
- return
- }
- this.currentRuleRegions.push(region)
- },
- removeRegion(index) {
- this.currentRuleRegions.splice(index, 1)
- },
- async submitRegions() {
- if (!this.http() || !this.regionDialog.ruleId) return
- this.loading.saveRegions = true
- try {
- await this.http().post(`/limit_purchase/rule/${this.regionDialog.ruleId}/regions`, {
- regions: this.currentRuleRegions
- })
- this.$message.success('限购地区设置成功')
- this.regionDialog.visible = false
- this.fetchRuleList()
- } catch (e) {
- console.error(e)
- this.$message.error('保存限购地区失败')
- } finally {
- this.loading.saveRegions = false
- }
- },
- // ========== 工具方法 ==========
- formatRegionLabel(region) {
- const parts = []
- if (region.province_name) parts.push(region.province_name)
- if (region.city_name) parts.push(region.city_name)
- if (region.district_name) parts.push(region.district_name)
- return parts.join(' - ') || '未知地区'
- }
- }
- }
- </script>
- <style scoped>
- .limit-purchase-rule-container {
- padding: 0;
- }
- .rule-list-card {
- margin-bottom: 0;
- }
- .card-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- }
- .region-tag {
- margin-right: 4px;
- margin-bottom: 4px;
- }
- .text-muted {
- color: #999;
- }
- .danger-text {
- color: #f56c6c;
- }
- .region-selector-wrapper {
- padding: 10px 0 0;
- }
- .region-selector-title {
- display: flex;
- align-items: center;
- margin-bottom: 16px;
- font-size: 14px;
- }
- .region-selector-title .text-danger {
- color: #f56c6c;
- }
- .region-form {
- margin-bottom: 16px;
- }
- .limited-region-list {
- background: #fff7f7;
- border-radius: 4px;
- padding: 12px 16px;
- }
- .inner-region-form {
- margin-bottom: 8px;
- }
- .limited-region-list .list-title {
- font-size: 14px;
- margin-bottom: 8px;
- }
- .limited-region-list .empty-tip {
- font-size: 13px;
- color: #999;
- }
- .limited-area-item {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding: 8px 0;
- border-bottom: 1px solid #f5d1d1;
- }
- .limited-area-item:last-child {
- border-bottom: none;
- }
- .dialog-footer {
- text-align: right;
- }
- .text-right {
- text-align: right;
- }
- </style>
|