| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602 |
- <template>
- <view class="dict-container">
- <NavBar title="数据字典" @back="goBack" />
- <!-- 字典编码列表视图 -->
- <view class="dict-list-view" v-if="!showDetail">
- <!-- 搜索栏 -->
- <view class="search-bar">
- <input
- v-model="searchCode"
- class="search-input"
- placeholder="搜索字典编码"
- @confirm="handleSearch"
- />
- <view class="search-actions">
- <view class="btn-search" @click="handleSearch">搜索</view>
- <view class="btn-reset" @click="handleReset">重置</view>
- <view class="btn-create" @click="handleCreate">创建</view>
- </view>
- </view>
- <!-- 字典编码列表 -->
- <view class="code-list" v-if="codeList.length > 0">
- <view
- class="code-item"
- v-for="(item, index) in codeList"
- :key="index"
- @click="handleSelectDict(item)"
- >
- <view class="item-main">
- <text class="item-code">{{ item.code }}</text>
- <text class="item-remark" v-if="item.remark">{{ item.remark }}</text>
- </view>
- <view class="item-meta">
- <text class="item-count">{{ item.count }} 项</text>
- <AppIcon name="chevron-right" size="14" color="#CCCCCC" />
- </view>
- </view>
- </view>
- <!-- 加载中 -->
- <view class="loading-state" v-if="loading">
- <view class="loading-spinner"></view>
- <text class="loading-text">加载中...</text>
- </view>
- <!-- 空状态 -->
- <view class="empty-state" v-if="codeList.length === 0 && !loading">
- <view class="empty-icon-wrapper">
- <AppIcon name="book-open" size="48" color="#CCCCCC" />
- </view>
- <text class="empty-text">暂无字典数据</text>
- <view class="btn-create empty-btn" @click="handleCreate">创建字典</view>
- </view>
- </view>
- <!-- 字典编辑视图 -->
- <view class="dict-edit-view" v-if="showDetail">
- <view class="edit-header">
- <view class="back-row" @click="handleBackToList">
- <AppIcon name="chevron-left" size="18" color="#C6171E" />
- <text class="edit-title">{{ isNew ? '创建字典' : editForm.code }}</text>
- </view>
- </view>
- <view class="edit-form">
- <!-- 字典编码(新建时可编辑,已有不可改) -->
- <view class="form-row">
- <text class="form-label">字典编码</text>
- <input
- v-model="editForm.code"
- class="form-input"
- placeholder="请输入字典编码"
- :disabled="!isNew"
- />
- </view>
- <!-- 备注 -->
- <view class="form-row">
- <text class="form-label">备注</text>
- <input
- v-model="editForm.remark"
- class="form-input"
- placeholder="请输入备注"
- />
- </view>
- <!-- 字典项列表 -->
- <view class="items-section">
- <view class="items-header">
- <text class="items-title">字典项</text>
- </view>
- <view
- class="dict-item-row"
- v-for="(item, idx) in editForm.list"
- :key="idx"
- >
- <view class="item-fields">
- <view class="field-group">
- <text class="field-label">名称</text>
- <input
- v-model="item.name"
- class="field-input"
- placeholder="显示名称"
- @input="markDirty"
- />
- </view>
- <view class="field-group">
- <text class="field-label">码值</text>
- <input
- v-model="item.value"
- class="field-input"
- placeholder="数据码值"
- :disabled="!!item.id"
- @input="markDirty"
- />
- </view>
- <view class="field-group field-sm">
- <text class="field-label">权重</text>
- <input
- v-model.number="item.weight"
- class="field-input"
- type="number"
- placeholder="排序"
- @input="markDirty"
- />
- </view>
- </view>
- <view class="item-delete" @click="handleDeleteItem(idx)">
- <AppIcon name="trash" size="18" color="#FF4D4F" />
- </view>
- </view>
- <view class="add-item-btn" @click="handleAddItem">
- <AppIcon name="plus" size="16" color="#C6171E" />
- <text>新增字典项</text>
- </view>
- </view>
- <!-- 保存按钮 -->
- <view class="save-section" v-if="isDirty">
- <view class="btn-save" @click="handleSave">保存</view>
- </view>
- </view>
- </view>
- </view>
- </template>
- <script setup>
- import { ref, onMounted } from 'vue'
- import { getDataDictList, saveOrUpdateDict } from '../../api/dict.js'
- import { showToast } from '../../utils/index.js'
- const searchCode = ref('')
- const codeList = ref([])
- const allDictData = ref([])
- const loading = ref(true)
- const showDetail = ref(false)
- const isNew = ref(false)
- const isDirty = ref(false)
- const editForm = ref({
- code: '',
- remark: '',
- list: []
- })
- const goBack = () => {
- if (showDetail.value) {
- handleBackToList()
- } else {
- uni.navigateBack()
- }
- }
- const loadData = async (code = '') => {
- loading.value = true
- try {
- const params = {}
- if (code) params.code = code
- const res = await getDataDictList(params)
- if (res && res.code === 200) {
- const data = res.data
- allDictData.value = data || []
- buildCodeList(allDictData.value)
- }
- } catch (e) {
- console.error('加载字典列表失败:', e)
- showToast('加载字典数据失败')
- } finally {
- loading.value = false
- }
- }
- const buildCodeList = (data) => {
- const seen = new Set()
- const result = []
- data.forEach(item => {
- if (!seen.has(item.code)) {
- seen.add(item.code)
- const items = data.filter(k => k.code === item.code)
- result.push({
- code: item.code,
- remark: item.remark || '',
- count: items.length
- })
- }
- })
- codeList.value = result
- }
- const handleSearch = () => {
- loadData(searchCode.value.trim())
- }
- const handleReset = () => {
- searchCode.value = ''
- loadData()
- }
- const handleCreate = () => {
- isNew.value = true
- isDirty.value = true
- editForm.value = {
- code: '',
- remark: '',
- list: [{ name: '', value: '', weight: 0 }]
- }
- showDetail.value = true
- }
- const handleSelectDict = (item) => {
- isNew.value = false
- isDirty.value = false
- const items = allDictData.value
- .filter(k => k.code === item.code)
- .sort((a, b) => (a.weight || 0) - (b.weight || 0))
- editForm.value = {
- code: item.code,
- remark: item.remark || '',
- list: items.map(k => ({
- id: k.id,
- name: k.name || '',
- value: k.value || '',
- weight: k.weight || 0
- }))
- }
- showDetail.value = true
- }
- const handleBackToList = () => {
- showDetail.value = false
- loadData(searchCode.value.trim())
- }
- const handleAddItem = () => {
- isDirty.value = true
- editForm.value.list.push({
- name: '',
- value: '',
- weight: 0
- })
- }
- const handleDeleteItem = (idx) => {
- isDirty.value = true
- editForm.value.list.splice(idx, 1)
- }
- const markDirty = () => {
- isDirty.value = true
- }
- const handleSave = async () => {
- // 校验
- if (!editForm.value.code) {
- showToast('字典编码不能为空')
- return
- }
- const hasEmptyName = editForm.value.list.some(k => !k.name)
- if (hasEmptyName) {
- showToast('字典项名称不能为空')
- return
- }
- const hasEmptyValue = editForm.value.list.some(k => !k.value && k.value !== 0)
- if (hasEmptyValue) {
- showToast('字典项码值不能为空')
- return
- }
- const params = editForm.value.list.map(k => ({
- id: k.id,
- name: k.name,
- value: k.value,
- weight: k.weight,
- code: editForm.value.code,
- remark: editForm.value.remark
- }))
- try {
- const res = await saveOrUpdateDict(params)
- if (res && res.code === 200) {
- showToast('保存成功', 'success')
- isDirty.value = false
- handleBackToList()
- }
- } catch (e) {
- console.error('保存字典失败:', e)
- showToast(e.msg || '保存失败')
- }
- }
- onMounted(() => loadData())
- </script>
- <style scoped>
- .dict-container {
- min-height: 100vh;
- background-color: #F5F7FA;
- padding-bottom: 100rpx;
- }
- /* ===== 搜索栏 ===== */
- .search-bar {
- background-color: #FFFFFF;
- padding: 20rpx;
- margin: 20rpx;
- border-radius: 20rpx;
- box-shadow: 0 1px 3px rgba(0,0,0,0.06);
- }
- .search-input {
- width: 100%;
- height: 76rpx;
- padding: 0 24rpx;
- border: 2rpx solid #E8E8E8;
- border-radius: 12rpx;
- font-size: 28rpx;
- color: #1A1A1A;
- background-color: #F5F7FA;
- box-sizing: border-box;
- margin-bottom: 16rpx;
- }
- .search-input:focus {
- border-color: #C6171E;
- background-color: #FFFFFF;
- }
- .search-actions {
- display: flex;
- gap: 16rpx;
- }
- .btn-search,
- .btn-reset,
- .btn-create {
- flex: 1;
- height: 64rpx;
- display: flex;
- align-items: center;
- justify-content: center;
- border-radius: 12rpx;
- font-size: 26rpx;
- font-weight: 500;
- transition: all 0.2s;
- }
- .btn-search {
- background-color: #C6171E;
- color: #FFFFFF;
- }
- .btn-reset {
- background-color: #F5F5F5;
- color: #666666;
- border: 2rpx solid #E8E8E8;
- }
- .btn-create {
- background-color: #C6171E;
- color: #FFFFFF;
- }
- .btn-search:active,
- .btn-create:active {
- transform: translateY(1px);
- opacity: 0.9;
- }
- .btn-reset:active {
- background-color: #E8E8E8;
- }
- /* ===== 编码列表 ===== */
- .code-list {
- padding: 0 20rpx;
- }
- .code-item {
- display: flex;
- justify-content: space-between;
- align-items: center;
- background-color: #FFFFFF;
- border-radius: 20rpx;
- padding: 28rpx;
- margin-bottom: 16rpx;
- box-shadow: 0 1px 3px rgba(0,0,0,0.06);
- }
- .code-item:active {
- transform: translateY(-2rpx);
- }
- .item-main {
- flex: 1;
- }
- .item-code {
- font-size: 30rpx;
- font-weight: 600;
- color: #1A1A1A;
- display: block;
- margin-bottom: 4rpx;
- }
- .item-remark {
- font-size: 24rpx;
- color: #999999;
- }
- .item-meta {
- display: flex;
- align-items: center;
- gap: 12rpx;
- }
- .item-count {
- font-size: 24rpx;
- color: #C6171E;
- background-color: rgba(198, 23, 30, 0.08);
- padding: 6rpx 16rpx;
- border-radius: 20rpx;
- }
- /* ===== 编辑视图 ===== */
- .edit-header {
- background-color: #FFFFFF;
- padding: 20rpx 30rpx;
- margin-bottom: 20rpx;
- }
- .back-row {
- display: flex;
- align-items: center;
- gap: 16rpx;
- }
- .edit-title {
- font-size: 30rpx;
- font-weight: 600;
- color: #1A1A1A;
- }
- .edit-form {
- padding: 0 20rpx;
- }
- .form-row {
- background-color: #FFFFFF;
- border-radius: 16rpx;
- padding: 20rpx 24rpx;
- margin-bottom: 16rpx;
- }
- .form-label {
- font-size: 26rpx;
- color: #999999;
- margin-bottom: 12rpx;
- display: block;
- }
- .form-input {
- width: 100%;
- height: 72rpx;
- padding: 0 20rpx;
- border: 2rpx solid #E8E8E8;
- border-radius: 12rpx;
- font-size: 28rpx;
- color: #1A1A1A;
- background-color: #F5F7FA;
- box-sizing: border-box;
- }
- .form-input:focus {
- border-color: #C6171E;
- background-color: #FFFFFF;
- }
- .form-input:disabled {
- background-color: #F0F0F0;
- color: #999999;
- }
- /* ===== 字典项 ===== */
- .items-section {
- background-color: #FFFFFF;
- border-radius: 16rpx;
- padding: 24rpx;
- margin-bottom: 24rpx;
- }
- .items-header {
- margin-bottom: 20rpx;
- }
- .items-title {
- font-size: 28rpx;
- font-weight: 600;
- color: #1A1A1A;
- }
- .dict-item-row {
- display: flex;
- align-items: flex-start;
- padding: 16rpx 0;
- border-bottom: 2rpx solid #F5F5F5;
- gap: 12rpx;
- }
- .dict-item-row:last-child {
- border-bottom: none;
- }
- .item-fields {
- flex: 1;
- display: flex;
- flex-direction: column;
- gap: 12rpx;
- }
- .field-group {
- display: flex;
- align-items: center;
- gap: 12rpx;
- }
- .field-label {
- width: 64rpx;
- font-size: 24rpx;
- color: #999999;
- flex-shrink: 0;
- }
- .field-input {
- flex: 1;
- height: 60rpx;
- padding: 0 16rpx;
- border: 2rpx solid #E8E8E8;
- border-radius: 10rpx;
- font-size: 26rpx;
- color: #1A1A1A;
- background-color: #F5F7FA;
- box-sizing: border-box;
- }
- .field-input:focus {
- border-color: #C6171E;
- background-color: #FFFFFF;
- }
- .field-input:disabled {
- background-color: #F0F0F0;
- color: #999999;
- }
- .field-sm .field-label {
- width: 64rpx;
- }
- .item-delete {
- padding: 30rpx 8rpx 0;
- flex-shrink: 0;
- }
- /* ===== 新增按钮 & 保存 ===== */
- .add-item-btn {
- display: flex;
- align-items: center;
- justify-content: center;
- gap: 8rpx;
- height: 72rpx;
- margin-top: 16rpx;
- border: 2rpx dashed #C6171E;
- border-radius: 12rpx;
- color: #C6171E;
- font-size: 26rpx;
- }
- .add-item-btn:active {
- background-color: rgba(198, 23, 30, 0.05);
- }
- .save-section {
- margin: 24rpx 0 40rpx;
- }
- .btn-save {
- width: 100%;
- height: 88rpx;
- display: flex;
- align-items: center;
- justify-content: center;
- background-color: #C6171E;
- color: #FFFFFF;
- border-radius: 16rpx;
- font-size: 32rpx;
- font-weight: 600;
- box-shadow: 0 6rpx 20rpx rgba(198, 23, 30, 0.35);
- }
- .btn-save:active {
- transform: translateY(2rpx);
- box-shadow: 0 2rpx 10rpx rgba(198, 23, 30, 0.25);
- }
- /* ===== 空状态 ===== */
- .empty-btn {
- width: auto;
- padding: 16rpx 48rpx;
- margin-top: 24rpx;
- background-color: #C6171E;
- color: #FFFFFF;
- border-radius: 12rpx;
- font-size: 28rpx;
- font-weight: 500;
- }
- </style>
|