refactor: rename the theme brand and every identifier from AKSRT to Jio
Full-project rename. `git grep -i aksrt` is now empty apart from the
DBMigrator 3.3.0 changelog note that documents the rename itself.
Code identifiers
- PHP namespace AKSRT\ -> Jio\ (composer PSR-4 + 378 declarations)
- global constants AKSRT_* -> JIO_* (VERSION/DIR/URI, PAY_TYPE_*, CAPTCHA_*)
- procedural functions, hooks, options, meta keys, table names,
cookies, query vars, ajax actions and nonces: aksrt_ -> jio_
- text domain aksrt -> jio; Theme Name -> Jio
- JS globals: aksrtData/aksrtAdminData/... -> jioData/jioAdminData/...,
window.AKSRTCaptcha & friends -> window.JioCaptcha, Aksrt* -> Jio*
- Vue components Aks*.vue -> Jio*.vue (15 files renamed + imports)
- CSS classes/data-attrs/custom properties: aksrt- -> jio-
- REST namespace aksrt/v1 -> jio/v1 (incl. payment gateway callbacks)
Storage layer
- DB tables {prefix}aksrt_* -> {prefix}jio_*; SCHEMA_VERSION 3.2.0 -> 3.3.0
so dbDelta creates the new names on install
- widget id_base aksrt_widget_* -> jio_widget_*
- uninstall.php drop list rebuilt from the actual migrator tables
(dropped 3 stale entries, added the 17 missing ones)
Removed
- hardcoded https://api.aksrt.com/version.json default update endpoint;
get_endpoint() now reads the 「更新端点」 setting and do_check() no-ops
when it is empty
Files renamed: inc/helpers/aksrt-*.php -> jio-*.php (54),
assets/admin/src/components/form/Aks*.vue -> Jio*.vue (15).
Build artifacts regenerated: assets/css/build, assets/js/runtime,
assets/admin/build.
Verified: audit-psr4-paths errors=0, audit-admin-contracts errors=0
(warnings=56, unchanged baseline), 813/813 widget-system tests,
44/44 phpunit, php -l clean across all PHP.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
5b16bc6e15
commit
35bb96e82f
997 files changed
+28216
-28188
No files matched your search
@@ -1,35 +1,35 @@
|
||||
<template>
|
||||
<div class="aksrt-deferred-page">
|
||||
<div class="aksrt-deferred-page__icon">
|
||||
<div class="jio-deferred-page">
|
||||
<div class="jio-deferred-page__icon">
|
||||
<FaIcon :icon="resolvedIcon" :size="48" />
|
||||
</div>
|
||||
<h2 class="aksrt-deferred-page__title">{{ resolvedTitle }}</h2>
|
||||
<p class="aksrt-deferred-page__desc">{{ resolvedDesc }}</p>
|
||||
<div class="aksrt-deferred-page__meta">
|
||||
<h2 class="jio-deferred-page__title">{{ resolvedTitle }}</h2>
|
||||
<p class="jio-deferred-page__desc">{{ resolvedDesc }}</p>
|
||||
<div class="jio-deferred-page__meta">
|
||||
<n-tag size="small" type="info">{{ resolvedStatus }}</n-tag>
|
||||
<n-tag v-if="resolvedPhase" size="small" type="warning">Phase {{ resolvedPhase }}</n-tag>
|
||||
</div>
|
||||
<div v-if="resolvedDependsOn.length" class="aksrt-deferred-page__depends">
|
||||
<span class="aksrt-deferred-page__label">依赖</span>
|
||||
<div class="aksrt-deferred-page__depends-list">
|
||||
<div v-if="resolvedDependsOn.length" class="jio-deferred-page__depends">
|
||||
<span class="jio-deferred-page__label">依赖</span>
|
||||
<div class="jio-deferred-page__depends-list">
|
||||
<n-tag v-for="item in resolvedDependsOn" :key="item" size="small" type="default">{{ item }}</n-tag>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="resolvedReason" class="aksrt-deferred-page__reason">
|
||||
<span class="aksrt-deferred-page__label">原因</span>
|
||||
<div v-if="resolvedReason" class="jio-deferred-page__reason">
|
||||
<span class="jio-deferred-page__label">原因</span>
|
||||
<p>{{ resolvedReason }}</p>
|
||||
</div>
|
||||
<div v-if="resolvedFeatures && resolvedFeatures.length" class="aksrt-deferred-page__features">
|
||||
<div v-if="resolvedFeatures && resolvedFeatures.length" class="jio-deferred-page__features">
|
||||
<div
|
||||
v-for="(feat, i) in resolvedFeatures"
|
||||
:key="i"
|
||||
class="aksrt-deferred-page__feature"
|
||||
class="jio-deferred-page__feature"
|
||||
>
|
||||
<FaIcon icon="fa fas fa-check-circle" :size="14" />
|
||||
<span>{{ feat }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="aksrt-deferred-page__badge">
|
||||
<div class="jio-deferred-page__badge">
|
||||
<n-tag type="info" size="small">即将推出</n-tag>
|
||||
</div>
|
||||
</div>
|
||||
@@ -78,7 +78,7 @@ const resolvedFeatures = computed(() => {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.aksrt-deferred-page {
|
||||
.jio-deferred-page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
@@ -88,75 +88,75 @@ const resolvedFeatures = computed(() => {
|
||||
padding: 40px 20px;
|
||||
}
|
||||
|
||||
.aksrt-deferred-page__icon {
|
||||
color: var(--aksrt-color-border-strong);
|
||||
.jio-deferred-page__icon {
|
||||
color: var(--jio-color-border-strong);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.aksrt-deferred-page__title {
|
||||
.jio-deferred-page__title {
|
||||
margin: 0 0 10px;
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: var(--aksrt-color-text);
|
||||
color: var(--jio-color-text);
|
||||
}
|
||||
|
||||
.aksrt-deferred-page__desc {
|
||||
.jio-deferred-page__desc {
|
||||
margin: 0 0 20px;
|
||||
font-size: 14px;
|
||||
color: var(--aksrt-color-muted);
|
||||
color: var(--jio-color-muted);
|
||||
max-width: 400px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.aksrt-deferred-page__meta {
|
||||
.jio-deferred-page__meta {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
.aksrt-deferred-page__depends,
|
||||
.aksrt-deferred-page__reason {
|
||||
.jio-deferred-page__depends,
|
||||
.jio-deferred-page__reason {
|
||||
width: min(520px, 100%);
|
||||
margin-bottom: 18px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.aksrt-deferred-page__label {
|
||||
.jio-deferred-page__label {
|
||||
display: inline-block;
|
||||
margin-bottom: 8px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: var(--aksrt-color-text-secondary);
|
||||
color: var(--jio-color-text-secondary);
|
||||
}
|
||||
|
||||
.aksrt-deferred-page__depends-list {
|
||||
.jio-deferred-page__depends-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.aksrt-deferred-page__reason p {
|
||||
.jio-deferred-page__reason p {
|
||||
margin: 0;
|
||||
color: var(--aksrt-color-muted);
|
||||
color: var(--jio-color-muted);
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.aksrt-deferred-page__features {
|
||||
.jio-deferred-page__features {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.aksrt-deferred-page__feature {
|
||||
.jio-deferred-page__feature {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 13px;
|
||||
color: var(--aksrt-color-muted);
|
||||
color: var(--jio-color-muted);
|
||||
}
|
||||
|
||||
.aksrt-deferred-page__feature :deep(i) {
|
||||
.jio-deferred-page__feature :deep(i) {
|
||||
color: #52c41a;
|
||||
}
|
||||
</style>
|
||||
@@ -1,10 +1,10 @@
|
||||
<template>
|
||||
<div class="aksrt-empty-state">
|
||||
<div class="aksrt-empty-state__icon">
|
||||
<div class="jio-empty-state">
|
||||
<div class="jio-empty-state__icon">
|
||||
<i :class="icon" style="font-size: 48px;"></i>
|
||||
</div>
|
||||
<h3 class="aksrt-empty-state__title">{{ title }}</h3>
|
||||
<p class="aksrt-empty-state__desc">{{ desc }}</p>
|
||||
<h3 class="jio-empty-state__title">{{ title }}</h3>
|
||||
<p class="jio-empty-state__desc">{{ desc }}</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -17,7 +17,7 @@ defineProps({
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.aksrt-empty-state {
|
||||
.jio-empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
@@ -26,22 +26,22 @@ defineProps({
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.aksrt-empty-state__icon {
|
||||
.jio-empty-state__icon {
|
||||
margin-bottom: 20px;
|
||||
color: var(--aksrt-color-border-strong);
|
||||
color: var(--jio-color-border-strong);
|
||||
}
|
||||
|
||||
.aksrt-empty-state__title {
|
||||
.jio-empty-state__title {
|
||||
margin: 0 0 8px;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: var(--aksrt-color-text);
|
||||
color: var(--jio-color-text);
|
||||
}
|
||||
|
||||
.aksrt-empty-state__desc {
|
||||
.jio-empty-state__desc {
|
||||
margin: 0;
|
||||
font-size: 13px;
|
||||
color: var(--aksrt-color-muted);
|
||||
color: var(--jio-color-muted);
|
||||
max-width: 360px;
|
||||
line-height: 1.65;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<header class="aksrt-mobile-topbar">
|
||||
<header class="jio-mobile-topbar">
|
||||
<button
|
||||
class="aksrt-mobile-topbar__menu"
|
||||
class="jio-mobile-topbar__menu"
|
||||
type="button"
|
||||
aria-label="打开菜单"
|
||||
@click="$emit('toggle')"
|
||||
@@ -13,35 +13,35 @@
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<div class="aksrt-mobile-topbar__title">{{ title }}</div>
|
||||
<div class="jio-mobile-topbar__title">{{ title }}</div>
|
||||
|
||||
<div class="aksrt-mobile-topbar__brand">AK</div>
|
||||
<div class="jio-mobile-topbar__brand">AK</div>
|
||||
</header>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
defineProps({
|
||||
title: { type: String, default: 'AKSRT' },
|
||||
title: { type: String, default: 'Jio' },
|
||||
})
|
||||
|
||||
defineEmits(['toggle'])
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.aksrt-mobile-topbar {
|
||||
.jio-mobile-topbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
height: 48px;
|
||||
padding: 0 14px;
|
||||
background: var(--aksrt-color-surface);
|
||||
border-bottom: 1px solid var(--aksrt-color-border);
|
||||
background: var(--jio-color-surface);
|
||||
border-bottom: 1px solid var(--jio-color-border);
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 50;
|
||||
}
|
||||
|
||||
.aksrt-mobile-topbar__menu {
|
||||
.jio-mobile-topbar__menu {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -50,26 +50,26 @@ defineEmits(['toggle'])
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
background: transparent;
|
||||
color: var(--aksrt-color-text);
|
||||
color: var(--jio-color-text);
|
||||
cursor: pointer;
|
||||
margin-left: -6px;
|
||||
}
|
||||
|
||||
.aksrt-mobile-topbar__menu:hover {
|
||||
background: var(--aksrt-color-primary-soft);
|
||||
.jio-mobile-topbar__menu:hover {
|
||||
background: var(--jio-color-primary-soft);
|
||||
}
|
||||
|
||||
.aksrt-mobile-topbar__title {
|
||||
.jio-mobile-topbar__title {
|
||||
flex: 1 1 auto;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: var(--aksrt-color-text);
|
||||
color: var(--jio-color-text);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.aksrt-mobile-topbar__brand {
|
||||
.jio-mobile-topbar__brand {
|
||||
flex: 0 0 auto;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
@@ -77,7 +77,7 @@ defineEmits(['toggle'])
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 8px;
|
||||
background: var(--aksrt-color-primary);
|
||||
background: var(--jio-color-primary);
|
||||
color: #fff;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
<template>
|
||||
<header class="aksrt-page-header">
|
||||
<div class="aksrt-page-header__main">
|
||||
<h2 class="aksrt-page-header__title">{{ title }}</h2>
|
||||
<p v-if="desc" class="aksrt-page-header__desc">
|
||||
<header class="jio-page-header">
|
||||
<div class="jio-page-header__main">
|
||||
<h2 class="jio-page-header__title">{{ title }}</h2>
|
||||
<p v-if="desc" class="jio-page-header__desc">
|
||||
<slot name="desc">{{ desc }}</slot>
|
||||
</p>
|
||||
<div v-else-if="$slots.desc" class="aksrt-page-header__desc">
|
||||
<div v-else-if="$slots.desc" class="jio-page-header__desc">
|
||||
<slot name="desc" />
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="$slots.actions" class="aksrt-page-header__actions">
|
||||
<div v-if="$slots.actions" class="jio-page-header__actions">
|
||||
<slot name="actions" />
|
||||
</div>
|
||||
</header>
|
||||
@@ -23,7 +23,7 @@ defineProps({
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.aksrt-page-header {
|
||||
.jio-page-header {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
@@ -32,28 +32,28 @@ defineProps({
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.aksrt-page-header__main {
|
||||
.jio-page-header__main {
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.aksrt-page-header__title {
|
||||
.jio-page-header__title {
|
||||
margin: 0 0 6px;
|
||||
color: var(--aksrt-color-text);
|
||||
color: var(--jio-color-text);
|
||||
font-size: 22px;
|
||||
font-weight: 700;
|
||||
letter-spacing: -0.02em;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.aksrt-page-header__desc {
|
||||
.jio-page-header__desc {
|
||||
margin: 0;
|
||||
color: var(--aksrt-color-muted);
|
||||
color: var(--jio-color-muted);
|
||||
font-size: 13px;
|
||||
line-height: 1.65;
|
||||
}
|
||||
|
||||
.aksrt-page-header__actions {
|
||||
.jio-page-header__actions {
|
||||
flex: 0 0 auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -61,20 +61,20 @@ defineProps({
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.aksrt-page-header {
|
||||
.jio-page-header {
|
||||
margin-bottom: 14px;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.aksrt-page-header__title {
|
||||
.jio-page-header__title {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.aksrt-page-header__desc {
|
||||
.jio-page-header__desc {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.aksrt-page-header__actions {
|
||||
.jio-page-header__actions {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<n-form label-placement="left" label-width="110" class="aksrt-form float-common-fields">
|
||||
<n-form label-placement="left" label-width="110" class="jio-form float-common-fields">
|
||||
<n-form-item label="PC端显示" :show-feedback="false">
|
||||
<n-switch :value="!!modelValue.pc_s" @update:value="update('pc_s', $event)" />
|
||||
</n-form-item>
|
||||
@@ -10,7 +10,7 @@
|
||||
<div class="float-common-fields__colors">
|
||||
<div class="float-common-fields__color">
|
||||
<span>自定义图标颜色</span>
|
||||
<AksColorPicker
|
||||
<JioColorPicker
|
||||
:model-value="modelValue.color?.color || ''"
|
||||
alpha
|
||||
clearable
|
||||
@@ -19,7 +19,7 @@
|
||||
</div>
|
||||
<div class="float-common-fields__color">
|
||||
<span>自定义背景颜色</span>
|
||||
<AksColorPicker
|
||||
<JioColorPicker
|
||||
:model-value="modelValue.color?.bg || ''"
|
||||
alpha
|
||||
clearable
|
||||
@@ -33,7 +33,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import AksColorPicker from '@/components/form/AksColorPicker.vue'
|
||||
import JioColorPicker from '@/components/form/JioColorPicker.vue'
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: { type: Object, required: true },
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<template>
|
||||
<div class="float-btn-panel">
|
||||
<div class="aksrt-sortable__head" v-bind="headAttrs">
|
||||
<AksSortHandle @click.stop />
|
||||
<div class="aksrt-sortable__main">
|
||||
<div class="aksrt-sortable__title-row">
|
||||
<span class="aksrt-sortable__title">{{ meta.title }}</span>
|
||||
<div class="jio-sortable__head" v-bind="headAttrs">
|
||||
<JioSortHandle @click.stop />
|
||||
<div class="jio-sortable__main">
|
||||
<div class="jio-sortable__title-row">
|
||||
<span class="jio-sortable__title">{{ meta.title }}</span>
|
||||
<template v-if="modelValue.type !== 'more'">
|
||||
<n-tag v-if="modelValue.pc_s" size="small" type="info" :bordered="false">PC</n-tag>
|
||||
<n-tag v-if="modelValue.m_s" size="small" type="warning" :bordered="false">移动</n-tag>
|
||||
@@ -12,18 +12,18 @@
|
||||
</template>
|
||||
<n-tag v-else size="small" :bordered="false">{{ modelValue.buttons.length }} 个</n-tag>
|
||||
</div>
|
||||
<span v-if="meta.warn" class="aksrt-sortable__desc float-btn-panel__warn">
|
||||
<span v-if="meta.warn" class="jio-sortable__desc float-btn-panel__warn">
|
||||
<i class="fa fas fa-info-circle" aria-hidden="true" /> {{ meta.warn }}
|
||||
</span>
|
||||
</div>
|
||||
<i
|
||||
class="fa fas aksrt-sortable__toggle"
|
||||
class="fa fas jio-sortable__toggle"
|
||||
:class="expanded ? 'fa-chevron-down' : 'fa-chevron-right'"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-show="expanded" class="aksrt-sortable__body">
|
||||
<div v-show="expanded" class="jio-sortable__body">
|
||||
<div class="float-btn-panel__subtitle">{{ meta.panel }}</div>
|
||||
|
||||
<FloatMoreButtonsEditor
|
||||
@@ -35,7 +35,7 @@
|
||||
<template v-else>
|
||||
<FloatBtnCommonFields :model-value="modelValue" @update:model-value="emit('update:modelValue', $event)" />
|
||||
|
||||
<n-form label-placement="left" label-width="110" class="aksrt-form float-btn-panel__extra">
|
||||
<n-form label-placement="left" label-width="110" class="jio-form float-btn-panel__extra">
|
||||
<n-form-item v-if="modelValue.type === 'qrcode'" label="一句话简介">
|
||||
<n-input :value="modelValue.desc" @update:value="patch({ desc: $event })" />
|
||||
</n-form-item>
|
||||
@@ -49,7 +49,7 @@
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item v-if="modelValue.type === 'service_wechat'" label="微信二维码">
|
||||
<AksMediaField
|
||||
<JioMediaField
|
||||
:model-value="modelValue.wechat_img"
|
||||
placeholder="选择二维码图片"
|
||||
@update:model-value="patch({ wechat_img: $event })"
|
||||
@@ -62,11 +62,11 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
/** 悬浮按钮排序列表中的单个卡片;外层 .aksrt-sortable__item 由 FloatingButtonsSettings 提供。 */
|
||||
/** 悬浮按钮排序列表中的单个卡片;外层 .jio-sortable__item 由 FloatingButtonsSettings 提供。 */
|
||||
import { computed, ref } from 'vue'
|
||||
import FaIconPicker from '@/components/form/FaIconPicker.vue'
|
||||
import AksMediaField from '@/components/form/AksMediaField.vue'
|
||||
import AksSortHandle from '@/components/form/AksSortHandle.vue'
|
||||
import JioMediaField from '@/components/form/JioMediaField.vue'
|
||||
import JioSortHandle from '@/components/form/JioSortHandle.vue'
|
||||
import { collapseHeadAttrs } from '@/components/form/useCollapsibleItems.js'
|
||||
import FloatBtnCommonFields from './FloatBtnCommonFields.vue'
|
||||
import FloatMoreButtonsEditor from './FloatMoreButtonsEditor.vue'
|
||||
@@ -93,12 +93,12 @@ function patch(partial) {
|
||||
|
||||
<style scoped>
|
||||
.float-btn-panel__warn {
|
||||
color: var(--aksrt-color-warning, #D97706);
|
||||
color: var(--jio-color-warning, #D97706);
|
||||
}
|
||||
|
||||
.float-btn-panel__subtitle {
|
||||
margin-bottom: 12px;
|
||||
color: var(--aksrt-color-text);
|
||||
color: var(--jio-color-text);
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
</n-button>
|
||||
</template>
|
||||
|
||||
<n-form label-placement="left" label-width="110" class="aksrt-form">
|
||||
<n-form label-placement="left" label-width="110" class="jio-form">
|
||||
<n-form-item label="按钮简介">
|
||||
<n-input :value="btn.desc" @update:value="patch(index, { desc: $event })" />
|
||||
</n-form-item>
|
||||
|
||||
@@ -16,7 +16,7 @@ export const FLOAT_BTN_META = {
|
||||
more: { title: '更多按钮', panel: '添加更多自定义按钮' },
|
||||
}
|
||||
|
||||
const themeUri = (window.aksrtAdminData?.themeUri || '').replace(/\/$/, '')
|
||||
const themeUri = (window.jioAdminData?.themeUri || '').replace(/\/$/, '')
|
||||
|
||||
/** 「更多按钮」出厂默认项(对齐参考主题 float_btn.more 默认值)。 */
|
||||
export function giftMoreButton() {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="aksrt-fa-icon-picker">
|
||||
<div class="jio-fa-icon-picker">
|
||||
<n-popover v-model:show="popoverShow" trigger="click" placement="bottom-start" :width="460" :style="{ padding: 0 }">
|
||||
<template #trigger>
|
||||
<n-input
|
||||
@@ -19,8 +19,8 @@
|
||||
</n-input>
|
||||
</template>
|
||||
|
||||
<div class="aksrt-icon-picker__panel" @click="onPanelClick">
|
||||
<div class="aksrt-icon-picker__search">
|
||||
<div class="jio-icon-picker__panel" @click="onPanelClick">
|
||||
<div class="jio-icon-picker__search">
|
||||
<n-input v-model:value="search" placeholder="搜索图标..." size="small" clearable>
|
||||
<template #prefix>
|
||||
<FaIcon icon="fa fas fa-search" :size="12" />
|
||||
@@ -28,12 +28,12 @@
|
||||
</n-input>
|
||||
</div>
|
||||
|
||||
<div class="aksrt-icon-picker__tabs">
|
||||
<div class="jio-icon-picker__tabs">
|
||||
<button
|
||||
v-for="tab in tabs"
|
||||
:key="tab.id"
|
||||
type="button"
|
||||
class="aksrt-icon-picker__tab"
|
||||
class="jio-icon-picker__tab"
|
||||
:class="{ 'is-active': activeTab === tab.id }"
|
||||
@click="activeTab = tab.id"
|
||||
>
|
||||
@@ -42,23 +42,23 @@
|
||||
</div>
|
||||
|
||||
<!-- Custom SVG tab -->
|
||||
<div v-if="activeTab === 'custom'" class="aksrt-icon-picker__custom">
|
||||
<div v-if="activeTab === 'custom'" class="jio-icon-picker__custom">
|
||||
<n-input v-model:value="customCode" type="textarea" placeholder='<svg viewBox="0 0 1024 1024"><path d="…"></path></svg>' :rows="4" />
|
||||
<div class="aksrt-icon-picker__custom-actions">
|
||||
<span class="aksrt-icon-picker__custom-preview" v-html="customPreview"></span>
|
||||
<div class="jio-icon-picker__custom-actions">
|
||||
<span class="jio-icon-picker__custom-preview" v-html="customPreview"></span>
|
||||
<n-button size="small" type="primary" :disabled="!customPreview" @click="insertCustom">
|
||||
确认插入
|
||||
</n-button>
|
||||
</div>
|
||||
<div
|
||||
v-if="sessionCustom.length"
|
||||
class="aksrt-icon-picker__grid aksrt-icon-picker__grid--session"
|
||||
data-aksrt-icon-grid="custom"
|
||||
class="jio-icon-picker__grid jio-icon-picker__grid--session"
|
||||
data-jio-icon-grid="custom"
|
||||
>
|
||||
<div
|
||||
v-for="(svg, i) in sessionCustom"
|
||||
:key="i"
|
||||
class="aksrt-icon-picker__item"
|
||||
class="jio-icon-picker__item"
|
||||
:class="{ 'is-active': modelValue === `customsvg-${svg}` }"
|
||||
title="customsvg"
|
||||
:data-icon="`customsvg-${svg}`"
|
||||
@@ -68,11 +68,11 @@
|
||||
</div>
|
||||
|
||||
<!-- Catalog tab -->
|
||||
<div v-else class="aksrt-icon-picker__grid" data-aksrt-icon-grid="catalog">
|
||||
<div v-else class="jio-icon-picker__grid" data-jio-icon-grid="catalog">
|
||||
<div
|
||||
v-for="icon in filteredIcons"
|
||||
:key="icon"
|
||||
class="aksrt-icon-picker__item"
|
||||
class="jio-icon-picker__item"
|
||||
:class="{ 'is-active': modelValue === composeIconValue(activeGroup, icon) }"
|
||||
:title="icon"
|
||||
:data-icon="composeIconValue(activeGroup, icon)"
|
||||
@@ -80,11 +80,11 @@
|
||||
<i v-if="activeGroup.type === 'fa'" :class="[activeTab, 'fa-' + icon]"></i>
|
||||
<span
|
||||
v-else
|
||||
class="aksrt-icon-picker__preview-svg"
|
||||
class="jio-icon-picker__preview-svg"
|
||||
v-html="zibsvgMarkup(`zibsvg-${icon}`)"
|
||||
></span>
|
||||
</div>
|
||||
<div v-if="filteredIcons.length === 0" class="aksrt-icon-picker__empty">
|
||||
<div v-if="filteredIcons.length === 0" class="jio-icon-picker__empty">
|
||||
未找到匹配图标
|
||||
</div>
|
||||
</div>
|
||||
@@ -96,7 +96,7 @@
|
||||
<script setup>
|
||||
/**
|
||||
* FaIconPicker — universal icon picker backed by the server catalog
|
||||
* (GET /aksrt/v1/icons/catalog): full FA5 free set, the theme's built-in
|
||||
* (GET /jio/v1/icons/catalog): full FA5 free set, the theme's built-in
|
||||
* SVG sprite keys and inline custom SVG values.
|
||||
*/
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
@@ -176,53 +176,53 @@ onMounted(async () => {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.aksrt-icon-picker__panel {
|
||||
/* The popover teleports to <body>, outside #aksrt-admin-app where the
|
||||
.jio-icon-picker__panel {
|
||||
/* The popover teleports to <body>, outside #jio-admin-app where the
|
||||
palette is declared — re-declare every token the panel consumes. */
|
||||
--aksrt-color-primary: #18A058;
|
||||
--aksrt-color-primary-hover: #36AD6A;
|
||||
--aksrt-color-primary-soft: rgba(24, 160, 88, 0.12);
|
||||
--aksrt-color-text-secondary: #465149;
|
||||
--aksrt-color-muted: #6F7A72;
|
||||
--aksrt-color-border: #E2E8E3;
|
||||
--jio-color-primary: #18A058;
|
||||
--jio-color-primary-hover: #36AD6A;
|
||||
--jio-color-primary-soft: rgba(24, 160, 88, 0.12);
|
||||
--jio-color-text-secondary: #465149;
|
||||
--jio-color-muted: #6F7A72;
|
||||
--jio-color-border: #E2E8E3;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.aksrt-icon-picker__search {
|
||||
.jio-icon-picker__search {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.aksrt-icon-picker__tabs {
|
||||
.jio-icon-picker__tabs {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.aksrt-icon-picker__tab {
|
||||
.jio-icon-picker__tab {
|
||||
padding: 3px 10px;
|
||||
font-size: 13px;
|
||||
line-height: 20px;
|
||||
border: 1px solid var(--aksrt-color-border);
|
||||
border: 1px solid var(--jio-color-border);
|
||||
border-radius: 4px;
|
||||
background: transparent;
|
||||
color: var(--aksrt-color-text-secondary);
|
||||
color: var(--jio-color-text-secondary);
|
||||
cursor: pointer;
|
||||
transition: all 0.15s;
|
||||
}
|
||||
|
||||
.aksrt-icon-picker__tab:hover {
|
||||
border-color: var(--aksrt-color-primary);
|
||||
color: var(--aksrt-color-primary);
|
||||
.jio-icon-picker__tab:hover {
|
||||
border-color: var(--jio-color-primary);
|
||||
color: var(--jio-color-primary);
|
||||
}
|
||||
|
||||
.aksrt-icon-picker__tab.is-active {
|
||||
border-color: var(--aksrt-color-primary);
|
||||
background: var(--aksrt-color-primary);
|
||||
.jio-icon-picker__tab.is-active {
|
||||
border-color: var(--jio-color-primary);
|
||||
background: var(--jio-color-primary);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.aksrt-icon-picker__grid {
|
||||
.jio-icon-picker__grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(8, 1fr);
|
||||
gap: 4px;
|
||||
@@ -230,7 +230,7 @@ onMounted(async () => {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.aksrt-icon-picker__item {
|
||||
.jio-icon-picker__item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -239,60 +239,60 @@ onMounted(async () => {
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
color: var(--aksrt-color-text-secondary);
|
||||
color: var(--jio-color-text-secondary);
|
||||
transition: all 0.15s;
|
||||
}
|
||||
|
||||
.aksrt-icon-picker__item:hover {
|
||||
background: var(--aksrt-color-primary-soft);
|
||||
color: var(--aksrt-color-primary);
|
||||
.jio-icon-picker__item:hover {
|
||||
background: var(--jio-color-primary-soft);
|
||||
color: var(--jio-color-primary);
|
||||
}
|
||||
|
||||
.aksrt-icon-picker__item.is-active {
|
||||
background: var(--aksrt-color-primary);
|
||||
.jio-icon-picker__item.is-active {
|
||||
background: var(--jio-color-primary);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.aksrt-icon-picker__item :deep(svg),
|
||||
.aksrt-icon-picker__preview-svg :deep(svg),
|
||||
.aksrt-icon-picker__preview-svg svg {
|
||||
.jio-icon-picker__item :deep(svg),
|
||||
.jio-icon-picker__preview-svg :deep(svg),
|
||||
.jio-icon-picker__preview-svg svg {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
fill: currentColor;
|
||||
}
|
||||
|
||||
.aksrt-icon-picker__preview-svg {
|
||||
.jio-icon-picker__preview-svg {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
.aksrt-icon-picker__empty {
|
||||
.jio-icon-picker__empty {
|
||||
grid-column: 1 / -1;
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
color: var(--aksrt-color-muted);
|
||||
color: var(--jio-color-muted);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.aksrt-icon-picker__custom-actions {
|
||||
.jio-icon-picker__custom-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.aksrt-icon-picker__custom-preview {
|
||||
.jio-icon-picker__custom-preview {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border: 1px dashed var(--aksrt-color-muted);
|
||||
border: 1px dashed var(--jio-color-muted);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.aksrt-icon-picker__grid--session {
|
||||
.jio-icon-picker__grid--session {
|
||||
margin-top: 10px;
|
||||
padding-top: 8px;
|
||||
border-top: 1px solid var(--aksrt-color-muted);
|
||||
border-top: 1px solid var(--jio-color-muted);
|
||||
}
|
||||
</style>
|
||||
@@ -4,13 +4,13 @@
|
||||
<!-- Built-in sprite value -->
|
||||
<span
|
||||
v-else-if="isZibsvgValue(value)"
|
||||
class="aksrt-icon-value__svg"
|
||||
class="jio-icon-value__svg"
|
||||
v-html="zibsvgMarkup(value)"
|
||||
></span>
|
||||
<!-- Inline custom SVG value -->
|
||||
<span
|
||||
v-else-if="isCustomSvgValue(value)"
|
||||
class="aksrt-icon-value__svg"
|
||||
class="jio-icon-value__svg"
|
||||
v-html="cleanSvg(value.slice(10))"
|
||||
></span>
|
||||
</template>
|
||||
@@ -35,12 +35,12 @@ const faClass = computed(() => {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.aksrt-icon-value__svg {
|
||||
.jio-icon-value__svg {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
.aksrt-icon-value__svg :deep(svg),
|
||||
.aksrt-icon-value__svg svg {
|
||||
.jio-icon-value__svg :deep(svg),
|
||||
.jio-icon-value__svg svg {
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
fill: currentColor;
|
||||
|
||||
File renamed without changes.
+1
-1
@@ -17,7 +17,7 @@
|
||||
<script setup>
|
||||
/**
|
||||
* 后台唯一取色器:所有颜色字段都经由此组件,输出与
|
||||
* PHP `AKSRT\Core\Color::sanitize($value, $allow_alpha)` 同一规则的规范值。
|
||||
* PHP `Jio\Core\Color::sanitize($value, $allow_alpha)` 同一规则的规范值。
|
||||
* 清空 / 非法值统一回传空串。
|
||||
*/
|
||||
import { computed } from 'vue'
|
||||
+9
-9
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<!-- Between number (min/max pair) -->
|
||||
<div class="aksrt-composite aksrt-composite--between">
|
||||
<div class="jio-composite jio-composite--between">
|
||||
<n-input-number
|
||||
:value="betweenValue('min')"
|
||||
:min="schema.min"
|
||||
@@ -11,7 +11,7 @@
|
||||
style="flex: 1"
|
||||
@update:value="update('min', $event)"
|
||||
/>
|
||||
<span class="aksrt-composite__sep">—</span>
|
||||
<span class="jio-composite__sep">—</span>
|
||||
<n-input-number
|
||||
:value="betweenValue('max')"
|
||||
:min="schema.min"
|
||||
@@ -22,7 +22,7 @@
|
||||
style="flex: 1"
|
||||
@update:value="update('max', $event)"
|
||||
/>
|
||||
<span v-if="schema.unit" class="aksrt-composite__unit">{{ schema.unit }}</span>
|
||||
<span v-if="schema.unit" class="jio-composite__unit">{{ schema.unit }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -52,23 +52,23 @@ function betweenValue(part) {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.aksrt-composite {
|
||||
.jio-composite {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.aksrt-composite--between {
|
||||
.jio-composite--between {
|
||||
max-width: 360px;
|
||||
}
|
||||
|
||||
.aksrt-composite__sep {
|
||||
color: var(--aksrt-color-border-strong);
|
||||
.jio-composite__sep {
|
||||
color: var(--jio-color-border-strong);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.aksrt-composite__unit {
|
||||
color: var(--aksrt-color-muted);
|
||||
.jio-composite__unit {
|
||||
color: var(--jio-color-muted);
|
||||
font-size: 13px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
+137
-137
@@ -1,19 +1,19 @@
|
||||
<template>
|
||||
<div class="aksrt-field" :class="fieldClasses">
|
||||
<div v-if="schema.label" class="aksrt-field__label-row">
|
||||
<label class="aksrt-field__label">
|
||||
<div class="jio-field" :class="fieldClasses">
|
||||
<div v-if="schema.label" class="jio-field__label-row">
|
||||
<label class="jio-field__label">
|
||||
{{ schema.label }}
|
||||
<span v-if="schema.required" class="aksrt-field__required">*</span>
|
||||
<span v-if="schema.required" class="jio-field__required">*</span>
|
||||
</label>
|
||||
<n-tooltip v-if="schema.help" trigger="hover">
|
||||
<template #trigger>
|
||||
<FaIcon icon="fa fas fa-question-circle" :size="13" class="aksrt-field__help-icon" />
|
||||
<FaIcon icon="fa fas fa-question-circle" :size="13" class="jio-field__help-icon" />
|
||||
</template>
|
||||
{{ schema.help }}
|
||||
</n-tooltip>
|
||||
</div>
|
||||
|
||||
<p v-if="schema.description && !schema.help" class="aksrt-field__desc">
|
||||
<p v-if="schema.description && !schema.help" class="jio-field__desc">
|
||||
{{ schema.description }}
|
||||
</p>
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
/>
|
||||
|
||||
<!-- Input (text, email, url, password) -->
|
||||
<div v-else-if="schema.type === 'input' || schema.type === 'text' || schema.type === 'url'" class="aksrt-field__input-wrap">
|
||||
<div v-else-if="schema.type === 'input' || schema.type === 'text' || schema.type === 'url'" class="jio-field__input-wrap">
|
||||
<n-input
|
||||
:value="secretDisplayValue"
|
||||
:placeholder="schema.placeholder || ''"
|
||||
@@ -36,11 +36,11 @@
|
||||
<template v-if="schema.prefix" #prefix>{{ schema.prefix }}</template>
|
||||
<template v-if="schema.suffix" #suffix>{{ schema.suffix }}</template>
|
||||
</n-input>
|
||||
<AksSecretClear :visible="secretClearVisible" :marked="secretMarkedForClear" @toggle="toggleSecretClear" />
|
||||
<JioSecretClear :visible="secretClearVisible" :marked="secretMarkedForClear" @toggle="toggleSecretClear" />
|
||||
</div>
|
||||
|
||||
<!-- Textarea(secret 时不回显已配置值,聚焦后输入新值即覆盖) -->
|
||||
<div v-else-if="schema.type === 'textarea'" class="aksrt-field__input-wrap">
|
||||
<div v-else-if="schema.type === 'textarea'" class="jio-field__input-wrap">
|
||||
<n-input
|
||||
:value="secretTextareaValue"
|
||||
type="textarea"
|
||||
@@ -49,11 +49,11 @@
|
||||
:disabled="schema.disabled"
|
||||
@update:value="emit('update:modelValue', $event)"
|
||||
/>
|
||||
<AksSecretClear :visible="secretClearVisible" :marked="secretMarkedForClear" @toggle="toggleSecretClear" />
|
||||
<JioSecretClear :visible="secretClearVisible" :marked="secretMarkedForClear" @toggle="toggleSecretClear" />
|
||||
</div>
|
||||
|
||||
<!-- Number -->
|
||||
<div v-else-if="schema.type === 'number' || schema.type === 'spinner'" class="aksrt-field__number">
|
||||
<div v-else-if="schema.type === 'number' || schema.type === 'spinner'" class="jio-field__number">
|
||||
<n-input-number
|
||||
:value="modelValue"
|
||||
:min="schema.min"
|
||||
@@ -64,11 +64,11 @@
|
||||
style="width: 100%"
|
||||
@update:value="emit('update:modelValue', $event)"
|
||||
/>
|
||||
<span v-if="schema.unit" class="aksrt-field__unit">{{ schema.unit }}</span>
|
||||
<span v-if="schema.unit" class="jio-field__unit">{{ schema.unit }}</span>
|
||||
</div>
|
||||
|
||||
<!-- Select -->
|
||||
<div v-else-if="schema.type === 'select'" class="aksrt-field__ordered-select">
|
||||
<div v-else-if="schema.type === 'select'" class="jio-field__ordered-select">
|
||||
<n-select
|
||||
:value="modelValue"
|
||||
:options="normalizedOptions"
|
||||
@@ -78,7 +78,7 @@
|
||||
:disabled="schema.disabled"
|
||||
@update:value="emit('update:modelValue', $event)"
|
||||
/>
|
||||
<AksSelectedOrder
|
||||
<JioSelectedOrder
|
||||
v-if="schema.multiple && schema.sortable && Array.isArray(modelValue)"
|
||||
:model-value="modelValue"
|
||||
:options="normalizedOptions"
|
||||
@@ -87,7 +87,7 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-else-if="schema.type === 'async_select'" class="aksrt-field__ordered-select">
|
||||
<div v-else-if="schema.type === 'async_select'" class="jio-field__ordered-select">
|
||||
<n-select
|
||||
:value="schema.multiple === true && !Array.isArray(modelValue) ? [] : modelValue"
|
||||
:options="asyncOptions"
|
||||
@@ -100,7 +100,7 @@
|
||||
@search="handleAsyncSearch"
|
||||
@update:value="emit('update:modelValue', $event)"
|
||||
/>
|
||||
<AksSelectedOrder
|
||||
<JioSelectedOrder
|
||||
v-if="schema.multiple && schema.sortable && Array.isArray(modelValue)"
|
||||
:model-value="modelValue"
|
||||
:options="asyncOptions"
|
||||
@@ -199,7 +199,7 @@
|
||||
</n-checkbox-group>
|
||||
|
||||
<!-- Color picker -->
|
||||
<AksColorPicker
|
||||
<JioColorPicker
|
||||
v-else-if="schema.type === 'color'"
|
||||
:model-value="typeof modelValue === 'string' ? modelValue : ''"
|
||||
:alpha="!!schema.showAlpha"
|
||||
@@ -208,12 +208,12 @@
|
||||
@update:model-value="emit('update:modelValue', $event)"
|
||||
/>
|
||||
|
||||
<div v-else-if="schema.type === 'palette'" class="aksrt-field__palette">
|
||||
<div v-else-if="schema.type === 'palette'" class="jio-field__palette">
|
||||
<button
|
||||
v-for="opt in normalizedOptions"
|
||||
:key="opt.value"
|
||||
type="button"
|
||||
class="aksrt-field__palette-item"
|
||||
class="jio-field__palette-item"
|
||||
:class="{ 'is-active': modelValue === opt.value }"
|
||||
:style="{ background: opt.label }"
|
||||
:title="opt.value"
|
||||
@@ -230,7 +230,7 @@
|
||||
/>
|
||||
|
||||
<!-- Media upload -->
|
||||
<AksMediaField
|
||||
<JioMediaField
|
||||
v-else-if="schema.type === 'media' || schema.type === 'upload'"
|
||||
:model-value="modelValue"
|
||||
:accept="schema.accept"
|
||||
@@ -239,7 +239,7 @@
|
||||
/>
|
||||
|
||||
<!-- WordPress image gallery (attachment ID array) -->
|
||||
<AksGalleryField
|
||||
<JioGalleryField
|
||||
v-else-if="schema.type === 'gallery'"
|
||||
:model-value="Array.isArray(modelValue) ? modelValue : []"
|
||||
:title="schema.label || '选择图片'"
|
||||
@@ -259,7 +259,7 @@
|
||||
/>
|
||||
|
||||
<!-- Code editor with syntax highlighting (CodeMirror 6) -->
|
||||
<AksCodeEditor
|
||||
<JioCodeEditor
|
||||
v-else-if="schema.type === 'code'"
|
||||
:model-value="modelValue"
|
||||
:language="schema.language || 'html'"
|
||||
@@ -281,7 +281,7 @@
|
||||
<!-- Pure HTML content (no form field, just display) -->
|
||||
<div
|
||||
v-else-if="schema.type === 'content'"
|
||||
class="aksrt-field__content"
|
||||
class="jio-field__content"
|
||||
v-html="schema.content || modelValue || ''"
|
||||
/>
|
||||
|
||||
@@ -316,7 +316,7 @@
|
||||
/>
|
||||
|
||||
<!-- Object composite: between_number (min/max pair) -->
|
||||
<AksCompositeField
|
||||
<JioCompositeField
|
||||
v-else-if="schema.type === 'between_number'"
|
||||
:schema="schema"
|
||||
:model-value="modelValue"
|
||||
@@ -324,7 +324,7 @@
|
||||
/>
|
||||
|
||||
<!-- Image select (radio with image preview) -->
|
||||
<AksImageSelectField
|
||||
<JioImageSelectField
|
||||
v-else-if="schema.type === 'image_select'"
|
||||
:model-value="modelValue"
|
||||
:options="normalizedOptions"
|
||||
@@ -334,24 +334,24 @@
|
||||
/>
|
||||
|
||||
<!-- Fieldset (single nested object) -->
|
||||
<div v-else-if="schema.type === 'fieldset' || schema.type === 'accordion'" class="aksrt-field__fieldset">
|
||||
<div v-else-if="schema.type === 'fieldset' || schema.type === 'accordion'" class="jio-field__fieldset">
|
||||
<template v-if="fieldsetSections.length">
|
||||
<div
|
||||
v-for="(section, sIndex) in fieldsetSections"
|
||||
:key="section.title"
|
||||
class="aksrt-field__accordion-section"
|
||||
class="jio-field__accordion-section"
|
||||
:class="{ 'is-collapsed': !isFieldsetSectionOpen(sIndex) }"
|
||||
>
|
||||
<div class="aksrt-field__accordion-section-head" @click="toggleFieldsetSection(sIndex)">
|
||||
<span class="aksrt-field__accordion-section-title">{{ section.title }}</span>
|
||||
<div class="jio-field__accordion-section-head" @click="toggleFieldsetSection(sIndex)">
|
||||
<span class="jio-field__accordion-section-title">{{ section.title }}</span>
|
||||
<FaIcon
|
||||
:icon="isFieldsetSectionOpen(sIndex) ? 'fa fas fa-chevron-down' : 'fa fas fa-chevron-right'"
|
||||
:size="12"
|
||||
class="aksrt-field__accordion-section-toggle"
|
||||
class="jio-field__accordion-section-toggle"
|
||||
/>
|
||||
</div>
|
||||
<div v-show="isFieldsetSectionOpen(sIndex)" class="aksrt-field__accordion-section-body">
|
||||
<AksField
|
||||
<div v-show="isFieldsetSectionOpen(sIndex)" class="jio-field__accordion-section-body">
|
||||
<JioField
|
||||
v-for="field in sectionFields(section)"
|
||||
:key="field.key"
|
||||
:schema="field"
|
||||
@@ -363,7 +363,7 @@
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="!fieldsetSections.length">
|
||||
<AksField
|
||||
<JioField
|
||||
v-for="field in visibleNestedFields"
|
||||
:key="field.key"
|
||||
:schema="field"
|
||||
@@ -374,7 +374,7 @@
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div v-else-if="schema.type === 'link'" class="aksrt-field__link">
|
||||
<div v-else-if="schema.type === 'link'" class="jio-field__link">
|
||||
<n-input :value="modelValue?.url || ''" placeholder="https://" @update:value="updateLink('url', $event)" />
|
||||
<n-select :value="modelValue?.target || ''" :options="linkTargetOptions" @update:value="updateLink('target', $event)" />
|
||||
<n-input :value="modelValue?.text || ''" placeholder="链接文字(可选)" @update:value="updateLink('text', $event)" />
|
||||
@@ -386,54 +386,54 @@
|
||||
v-bind="SORTABLE_LIST_ATTRS"
|
||||
item-key="key"
|
||||
:disabled="schema.disabled"
|
||||
class="aksrt-sortable"
|
||||
class="jio-sortable"
|
||||
>
|
||||
<template #item="{ element: item, index }">
|
||||
<div class="aksrt-sortable__item">
|
||||
<div class="jio-sortable__item">
|
||||
<div
|
||||
class="aksrt-sortable__head"
|
||||
class="jio-sortable__head"
|
||||
v-bind="item.fields?.length ? listItemHeadAttrs(item.key) : {}"
|
||||
>
|
||||
<AksSortHandle @click.stop />
|
||||
<span class="aksrt-sortable__index">{{ index + 1 }}</span>
|
||||
<span class="aksrt-sortable__main">
|
||||
<span class="aksrt-sortable__title">{{ item.label }}</span>
|
||||
<JioSortHandle @click.stop />
|
||||
<span class="jio-sortable__index">{{ index + 1 }}</span>
|
||||
<span class="jio-sortable__main">
|
||||
<span class="jio-sortable__title">{{ item.label }}</span>
|
||||
</span>
|
||||
<FaIcon
|
||||
v-if="item.fields?.length"
|
||||
:icon="isListItemOpen(item.key) ? 'fa fas fa-chevron-down' : 'fa fas fa-chevron-right'"
|
||||
:size="12"
|
||||
class="aksrt-sortable__toggle"
|
||||
class="jio-sortable__toggle"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="item.fields?.length" v-show="isListItemOpen(item.key)" class="aksrt-sortable__body aksrt-field__sortable-body">
|
||||
<AksField v-for="field in item.fields" :key="field.key" :schema="field" :model-value="modelValue?.[item.key]?.[field.key] ?? field.default" :form-data="formData" @update:model-value="updateSortable(item.key, field.key, $event)" />
|
||||
<div v-if="item.fields?.length" v-show="isListItemOpen(item.key)" class="jio-sortable__body jio-field__sortable-body">
|
||||
<JioField v-for="field in item.fields" :key="field.key" :schema="field" :model-value="modelValue?.[item.key]?.[field.key] ?? field.default" :form-data="formData" @update:model-value="updateSortable(item.key, field.key, $event)" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</draggable>
|
||||
|
||||
<!-- Repeater (array of objects) — inline expand (default, draggable) -->
|
||||
<div v-else-if="(schema.type === 'repeater' || schema.type === 'group') && schema.ui !== 'list-modal'" class="aksrt-field__repeater">
|
||||
<div v-else-if="(schema.type === 'repeater' || schema.type === 'group') && schema.ui !== 'list-modal'" class="jio-field__repeater">
|
||||
<draggable
|
||||
v-if="repeaterDragList.length"
|
||||
v-model="repeaterDragList"
|
||||
v-bind="SORTABLE_LIST_ATTRS"
|
||||
:item-key="repeaterDragKey"
|
||||
:disabled="schema.disabled"
|
||||
class="aksrt-sortable"
|
||||
class="jio-sortable"
|
||||
>
|
||||
<template #item="{ element, index }">
|
||||
<div class="aksrt-sortable__item">
|
||||
<div class="aksrt-sortable__head" v-bind="repeaterHeadAttrs(repeaterDragKey(element))">
|
||||
<AksSortHandle @click.stop />
|
||||
<span class="aksrt-sortable__index">{{ index + 1 }}</span>
|
||||
<span class="aksrt-sortable__main">
|
||||
<span class="aksrt-sortable__title aksrt-field__repeater-summary">
|
||||
<div class="jio-sortable__item">
|
||||
<div class="jio-sortable__head" v-bind="repeaterHeadAttrs(repeaterDragKey(element))">
|
||||
<JioSortHandle @click.stop />
|
||||
<span class="jio-sortable__index">{{ index + 1 }}</span>
|
||||
<span class="jio-sortable__main">
|
||||
<span class="jio-sortable__title jio-field__repeater-summary">
|
||||
{{ repeaterItemSummary(element) || '未填写' }}
|
||||
</span>
|
||||
</span>
|
||||
<div class="aksrt-sortable__actions" @click.stop>
|
||||
<div class="jio-sortable__actions" @click.stop>
|
||||
<n-button
|
||||
size="tiny"
|
||||
type="error"
|
||||
@@ -448,12 +448,12 @@
|
||||
<FaIcon
|
||||
:icon="isRepeaterItemOpen(repeaterDragKey(element)) ? 'fa fas fa-chevron-down' : 'fa fas fa-chevron-right'"
|
||||
:size="12"
|
||||
class="aksrt-sortable__toggle"
|
||||
class="jio-sortable__toggle"
|
||||
/>
|
||||
</div>
|
||||
<div v-show="isRepeaterItemOpen(repeaterDragKey(element))" class="aksrt-sortable__body aksrt-field__repeater-fields">
|
||||
<div v-show="isRepeaterItemOpen(repeaterDragKey(element))" class="jio-sortable__body jio-field__repeater-fields">
|
||||
<template v-for="field in (schema.fields || [])" :key="field.key">
|
||||
<AksField
|
||||
<JioField
|
||||
v-if="matchDependency(field.dependsOn, element)"
|
||||
:schema="field"
|
||||
:model-value="element[field.key]"
|
||||
@@ -466,7 +466,7 @@
|
||||
</div>
|
||||
</template>
|
||||
</draggable>
|
||||
<div v-else class="aksrt-sortable__empty">
|
||||
<div v-else class="jio-sortable__empty">
|
||||
<FaIcon icon="fa fas fa-inbox" :size="18" />
|
||||
<span>{{ schema.emptyText || '暂无数据' }}</span>
|
||||
</div>
|
||||
@@ -483,7 +483,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Repeater list-modal: draggable card list + modal editor -->
|
||||
<AksRepeaterListModal
|
||||
<JioRepeaterListModal
|
||||
v-else-if="(schema.type === 'repeater' || schema.type === 'group') && schema.ui === 'list-modal'"
|
||||
:schema="schema"
|
||||
:model-value="modelValue"
|
||||
@@ -503,35 +503,35 @@
|
||||
/>
|
||||
|
||||
<!-- Validation message -->
|
||||
<p v-if="errorMessage" class="aksrt-field__error">{{ errorMessage }}</p>
|
||||
<p v-if="errorMessage" class="jio-field__error">{{ errorMessage }}</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
/**
|
||||
* AksField — renders a single form field based on its schema definition.
|
||||
* JioField — renders a single form field based on its schema definition.
|
||||
*
|
||||
* Supported types: switch, input/text, textarea, number, select, radio,
|
||||
* checkbox, color, icon, media, gallery, slider, code, alert, fieldset, sortable,
|
||||
* repeater (inline|list-modal), custom (slot).
|
||||
*
|
||||
* 所有可拖拽列表(sortable / 已选值排序 / repeater)共用 sortable-list.css +
|
||||
* SORTABLE_LIST_ATTRS + AksSortHandle。
|
||||
* SORTABLE_LIST_ATTRS + JioSortHandle。
|
||||
*/
|
||||
import { computed, ref } from 'vue'
|
||||
import draggable from 'vuedraggable'
|
||||
import FaIcon from './FaIcon.vue'
|
||||
import FaIconPicker from './FaIconPicker.vue'
|
||||
import AksMediaField from './AksMediaField.vue'
|
||||
import AksGalleryField from './AksGalleryField.vue'
|
||||
import AksCodeEditor from './AksCodeEditor.vue'
|
||||
import AksCompositeField from './AksCompositeField.vue'
|
||||
import AksColorPicker from './AksColorPicker.vue'
|
||||
import AksImageSelectField from './AksImageSelectField.vue'
|
||||
import AksSortHandle from './AksSortHandle.vue'
|
||||
import AksSecretClear from './AksSecretClear.vue'
|
||||
import AksSelectedOrder from './AksSelectedOrder.vue'
|
||||
import AksRepeaterListModal from './AksRepeaterListModal.vue'
|
||||
import JioMediaField from './JioMediaField.vue'
|
||||
import JioGalleryField from './JioGalleryField.vue'
|
||||
import JioCodeEditor from './JioCodeEditor.vue'
|
||||
import JioCompositeField from './JioCompositeField.vue'
|
||||
import JioColorPicker from './JioColorPicker.vue'
|
||||
import JioImageSelectField from './JioImageSelectField.vue'
|
||||
import JioSortHandle from './JioSortHandle.vue'
|
||||
import JioSecretClear from './JioSecretClear.vue'
|
||||
import JioSelectedOrder from './JioSelectedOrder.vue'
|
||||
import JioRepeaterListModal from './JioRepeaterListModal.vue'
|
||||
import { buildFieldsetSections } from './fieldset-sections.js'
|
||||
import { matchDependency } from './field-dependency.js'
|
||||
import { SORTABLE_LIST_ATTRS } from './sortable-options.js'
|
||||
@@ -567,12 +567,12 @@ const {
|
||||
} = useFieldRemoteOptions(props)
|
||||
|
||||
const fieldClasses = computed(() => ({
|
||||
'aksrt-field--disabled': props.schema.disabled,
|
||||
'aksrt-field--inline': props.schema.inline,
|
||||
[`aksrt-field--${props.schema.type}`]: !!props.schema.type,
|
||||
'jio-field--disabled': props.schema.disabled,
|
||||
'jio-field--inline': props.schema.inline,
|
||||
[`jio-field--${props.schema.type}`]: !!props.schema.type,
|
||||
}))
|
||||
|
||||
const secretMarkedForClear = computed(() => props.schema.secret && props.modelValue === '__AKSRT_CLEAR_SECRET__')
|
||||
const secretMarkedForClear = computed(() => props.schema.secret && props.modelValue === '__JIO_CLEAR_SECRET__')
|
||||
const secretDisplayValue = computed(() => secretMarkedForClear.value ? '' : props.modelValue)
|
||||
/** 已配置密钥(服务端回传 '***'):textarea 不回显掩码,留空即保持原值。 */
|
||||
const secretConfigured = computed(() => props.schema.secret && props.modelValue === '***')
|
||||
@@ -580,7 +580,7 @@ const secretClearVisible = computed(() => secretConfigured.value || secretMarked
|
||||
const secretTextareaValue = computed(() => secretConfigured.value ? '' : secretDisplayValue.value)
|
||||
|
||||
function toggleSecretClear() {
|
||||
emit('update:modelValue', secretMarkedForClear.value ? '***' : '__AKSRT_CLEAR_SECRET__')
|
||||
emit('update:modelValue', secretMarkedForClear.value ? '***' : '__JIO_CLEAR_SECRET__')
|
||||
}
|
||||
|
||||
/** Normalize options: support string[], {label, value}[], and dynamic formData-key references */
|
||||
@@ -682,7 +682,7 @@ function updateSortable(itemKey, fieldKey, value) {
|
||||
emit('update:modelValue', { ...current, [itemKey]: { ...(current[itemKey] || {}), [fieldKey]: value } })
|
||||
}
|
||||
|
||||
// ============ Repeater(inline 展开;list-modal 见 AksRepeaterListModal) ============
|
||||
// ============ Repeater(inline 展开;list-modal 见 JioRepeaterListModal) ============
|
||||
const repeaterLength = computed(() => Array.isArray(props.modelValue) ? props.modelValue.length : 0)
|
||||
const reachedRepeaterMinimum = computed(() => repeaterLength.value <= Number(props.schema.min || 0))
|
||||
const reachedRepeaterMaximum = computed(() => Number(props.schema.max || 0) > 0 && repeaterLength.value >= Number(props.schema.max))
|
||||
@@ -754,165 +754,165 @@ function updateRepeaterItem(index, fieldKey, value) {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.aksrt-field {
|
||||
.jio-field {
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
.aksrt-field:last-child {
|
||||
.jio-field:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* 结构化分组(会员商品/续费商品/积分兑换/升级价格)与相邻字段之间留出更明确的间隔,
|
||||
* 避免两个分组上下贴在一起,和参考主题的留白一致。 */
|
||||
.aksrt-field--group,
|
||||
.aksrt-field--repeater,
|
||||
.aksrt-field--accordion,
|
||||
.aksrt-field--fieldset {
|
||||
.jio-field--group,
|
||||
.jio-field--repeater,
|
||||
.jio-field--accordion,
|
||||
.jio-field--fieldset {
|
||||
margin-bottom: 26px;
|
||||
}
|
||||
|
||||
.aksrt-field__input-wrap {
|
||||
.jio-field__input-wrap {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.aksrt-field__label-row {
|
||||
.jio-field__label-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.aksrt-field__label {
|
||||
.jio-field__label {
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: var(--aksrt-color-text-secondary);
|
||||
color: var(--jio-color-text-secondary);
|
||||
}
|
||||
|
||||
.aksrt-field__required {
|
||||
.jio-field__required {
|
||||
color: #d03050;
|
||||
margin-left: 2px;
|
||||
}
|
||||
|
||||
.aksrt-field__help-icon {
|
||||
color: var(--aksrt-color-border-strong);
|
||||
.jio-field__help-icon {
|
||||
color: var(--jio-color-border-strong);
|
||||
cursor: help;
|
||||
transition: color 0.15s;
|
||||
}
|
||||
|
||||
.aksrt-field__help-icon:hover {
|
||||
color: var(--aksrt-color-primary);
|
||||
.jio-field__help-icon:hover {
|
||||
color: var(--jio-color-primary);
|
||||
}
|
||||
|
||||
.aksrt-field__desc {
|
||||
.jio-field__desc {
|
||||
margin: -2px 0 8px;
|
||||
font-size: 12px;
|
||||
color: var(--aksrt-color-muted);
|
||||
color: var(--jio-color-muted);
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.aksrt-field__error {
|
||||
.jio-field__error {
|
||||
margin: 4px 0 0;
|
||||
font-size: 12px;
|
||||
color: #d03050;
|
||||
}
|
||||
|
||||
.aksrt-field--disabled {
|
||||
.jio-field--disabled {
|
||||
opacity: 0.6;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.aksrt-field__number {
|
||||
.jio-field__number {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
max-width: 360px;
|
||||
}
|
||||
|
||||
.aksrt-field__unit {
|
||||
color: var(--aksrt-color-muted);
|
||||
.jio-field__unit {
|
||||
color: var(--jio-color-muted);
|
||||
font-size: 13px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.aksrt-field__palette {
|
||||
.jio-field__palette {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.aksrt-field__palette-item {
|
||||
.jio-field__palette-item {
|
||||
width: 34px;
|
||||
height: 26px;
|
||||
padding: 0;
|
||||
border: 3px solid var(--aksrt-color-surface);
|
||||
border: 3px solid var(--jio-color-surface);
|
||||
border-radius: 3px;
|
||||
box-shadow: 0 0 0 1px var(--aksrt-color-border-strong);
|
||||
box-shadow: 0 0 0 1px var(--jio-color-border-strong);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.aksrt-field__palette-item.is-active {
|
||||
box-shadow: 0 0 0 2px var(--aksrt-color-primary);
|
||||
.jio-field__palette-item.is-active {
|
||||
box-shadow: 0 0 0 2px var(--jio-color-primary);
|
||||
}
|
||||
|
||||
.aksrt-field__link {
|
||||
.jio-field__link {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(220px, 1fr) 120px minmax(140px, .5fr);
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
/* sortable 卡片展开区:两列字段栅格(卡片本身见 styles/sortable-list.css) */
|
||||
.aksrt-field__sortable-body {
|
||||
.jio-field__sortable-body {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
/* Fieldset / Accordion */
|
||||
.aksrt-field__fieldset {
|
||||
.jio-field__fieldset {
|
||||
padding: 14px;
|
||||
border: 1px solid var(--aksrt-color-border);
|
||||
border: 1px solid var(--jio-color-border);
|
||||
border-radius: 8px;
|
||||
background: var(--aksrt-color-canvas);
|
||||
background: var(--jio-color-canvas);
|
||||
}
|
||||
|
||||
.aksrt-field__accordion-section + .aksrt-field__accordion-section {
|
||||
.jio-field__accordion-section + .jio-field__accordion-section {
|
||||
margin-top: 14px;
|
||||
}
|
||||
|
||||
.aksrt-field__accordion-section-head {
|
||||
.jio-field__accordion-section-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
padding: 8px 10px;
|
||||
border: 1px solid var(--aksrt-color-border);
|
||||
border: 1px solid var(--jio-color-border);
|
||||
border-radius: 6px;
|
||||
background: var(--aksrt-color-surface);
|
||||
background: var(--jio-color-surface);
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--aksrt-color-text);
|
||||
color: var(--jio-color-text);
|
||||
transition: border-color 0.15s, color 0.15s;
|
||||
}
|
||||
|
||||
.aksrt-field__accordion-section-head:hover {
|
||||
border-color: var(--aksrt-color-primary);
|
||||
background: var(--aksrt-color-primary-soft);
|
||||
color: var(--aksrt-color-primary);
|
||||
.jio-field__accordion-section-head:hover {
|
||||
border-color: var(--jio-color-primary);
|
||||
background: var(--jio-color-primary-soft);
|
||||
color: var(--jio-color-primary);
|
||||
}
|
||||
|
||||
.aksrt-field__accordion-section-title {
|
||||
.jio-field__accordion-section-title {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.aksrt-field__accordion-section-toggle {
|
||||
.jio-field__accordion-section-toggle {
|
||||
flex-shrink: 0;
|
||||
color: var(--aksrt-color-text-secondary);
|
||||
color: var(--jio-color-text-secondary);
|
||||
}
|
||||
|
||||
.aksrt-field__accordion-section-body {
|
||||
.jio-field__accordion-section-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
@@ -920,58 +920,58 @@ function updateRepeaterItem(index, fieldKey, value) {
|
||||
}
|
||||
|
||||
/* Repeater inline(卡片 / 手柄 / 拖拽态见 styles/sortable-list.css) */
|
||||
.aksrt-field__repeater {
|
||||
.jio-field__repeater {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.aksrt-field__repeater-fields {
|
||||
.jio-field__repeater-fields {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
/* Content field */
|
||||
.aksrt-field__content {
|
||||
.jio-field__content {
|
||||
font-size: 13px;
|
||||
color: var(--aksrt-color-text-secondary);
|
||||
color: var(--jio-color-text-secondary);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.aksrt-field__content :deep(p) {
|
||||
.jio-field__content :deep(p) {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.aksrt-field__content :deep(p + ul) {
|
||||
.jio-field__content :deep(p + ul) {
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
.aksrt-field__content :deep(ul) {
|
||||
.jio-field__content :deep(ul) {
|
||||
margin: 0;
|
||||
padding-left: 18px;
|
||||
line-height: 1.9;
|
||||
}
|
||||
|
||||
/* 说明文字的小号灰色段落与警示文字(schema content 里用 class 引用) */
|
||||
.aksrt-field__content :deep(.aksrt-hint) {
|
||||
color: var(--aksrt-color-muted);
|
||||
.jio-field__content :deep(.jio-hint) {
|
||||
color: var(--jio-color-muted);
|
||||
font-size: 12px;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.aksrt-field__content :deep(.aksrt-hint + .aksrt-hint) {
|
||||
.jio-field__content :deep(.jio-hint + .jio-hint) {
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
.aksrt-field__content :deep(.aksrt-warn) {
|
||||
color: var(--aksrt-color-warning, #d97706);
|
||||
.jio-field__content :deep(.jio-warn) {
|
||||
color: var(--jio-color-warning, #d97706);
|
||||
}
|
||||
|
||||
/* Mobile */
|
||||
@media (max-width: 768px) {
|
||||
.aksrt-field__link,
|
||||
.aksrt-field__sortable-body {
|
||||
.jio-field__link,
|
||||
.jio-field__sortable-body {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
+43
-43
@@ -1,26 +1,26 @@
|
||||
<template>
|
||||
<div class="aksrt-field-group" :class="{ 'is-collapsible': collapsible, 'is-collapsed': collapsible && collapsed }">
|
||||
<div class="jio-field-group" :class="{ 'is-collapsible': collapsible, 'is-collapsed': collapsible && collapsed }">
|
||||
<div
|
||||
v-if="title"
|
||||
class="aksrt-field-group__header"
|
||||
class="jio-field-group__header"
|
||||
:class="{ 'is-clickable': collapsible }"
|
||||
@click="collapsible ? toggleCollapse() : undefined"
|
||||
>
|
||||
<div class="aksrt-field-group__header-left">
|
||||
<FaIcon v-if="icon" :icon="icon" :size="16" class="aksrt-field-group__icon" />
|
||||
<div class="jio-field-group__header-left">
|
||||
<FaIcon v-if="icon" :icon="icon" :size="16" class="jio-field-group__icon" />
|
||||
<div>
|
||||
<h3 class="aksrt-field-group__title">{{ title }}</h3>
|
||||
<p v-if="description" class="aksrt-field-group__desc">{{ description }}</p>
|
||||
<h3 class="jio-field-group__title">{{ title }}</h3>
|
||||
<p v-if="description" class="jio-field-group__desc">{{ description }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="aksrt-field-group__header-right">
|
||||
<div v-if="$slots.actions" class="aksrt-field-group__actions" @click.stop>
|
||||
<div class="jio-field-group__header-right">
|
||||
<div v-if="$slots.actions" class="jio-field-group__actions" @click.stop>
|
||||
<slot name="actions" />
|
||||
</div>
|
||||
<button
|
||||
v-if="collapsible"
|
||||
type="button"
|
||||
class="aksrt-field-group__collapse-btn"
|
||||
class="jio-field-group__collapse-btn"
|
||||
:aria-expanded="(!collapsed).toString()"
|
||||
:title="collapsed ? '展开配置' : '收起配置'"
|
||||
@click.stop="toggleCollapse"
|
||||
@@ -34,8 +34,8 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<transition name="aksrt-collapse">
|
||||
<div v-show="!collapsed" class="aksrt-field-group__body">
|
||||
<transition name="jio-collapse">
|
||||
<div v-show="!collapsed" class="jio-field-group__body">
|
||||
<slot />
|
||||
</div>
|
||||
</transition>
|
||||
@@ -44,7 +44,7 @@
|
||||
|
||||
<script setup>
|
||||
/**
|
||||
* AksFieldGroup — card-style container for grouping related form fields.
|
||||
* JioFieldGroup — card-style container for grouping related form fields.
|
||||
* Supports collapsible sections, icon headers, and description text.
|
||||
*
|
||||
* defaultCollapsed only seeds the initial state; afterwards the user toggles freely.
|
||||
@@ -69,16 +69,16 @@ function toggleCollapse() {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.aksrt-field-group {
|
||||
.jio-field-group {
|
||||
margin-bottom: 20px;
|
||||
border: 1px solid var(--aksrt-color-border);
|
||||
border: 1px solid var(--jio-color-border);
|
||||
border-radius: 12px;
|
||||
background: var(--aksrt-color-surface);
|
||||
box-shadow: var(--aksrt-shadow-card);
|
||||
background: var(--jio-color-surface);
|
||||
box-shadow: var(--jio-shadow-card);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.aksrt-field-group__header {
|
||||
.jio-field-group__header {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
@@ -86,66 +86,66 @@ function toggleCollapse() {
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.aksrt-field-group__header.is-clickable {
|
||||
.jio-field-group__header.is-clickable {
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.aksrt-field-group__header.is-clickable:hover .aksrt-field-group__title {
|
||||
color: var(--aksrt-color-primary);
|
||||
.jio-field-group__header.is-clickable:hover .jio-field-group__title {
|
||||
color: var(--jio-color-primary);
|
||||
}
|
||||
|
||||
.aksrt-field-group.is-collapsed .aksrt-field-group__header {
|
||||
.jio-field-group.is-collapsed .jio-field-group__header {
|
||||
padding-bottom: 18px;
|
||||
}
|
||||
|
||||
.aksrt-field-group__header-left {
|
||||
.jio-field-group__header-left {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 10px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.aksrt-field-group__header-right {
|
||||
.jio-field-group__header-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.aksrt-field-group__icon {
|
||||
color: var(--aksrt-color-primary);
|
||||
.jio-field-group__icon {
|
||||
color: var(--jio-color-primary);
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.aksrt-field-group__title {
|
||||
.jio-field-group__title {
|
||||
margin: 0;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: var(--aksrt-color-text);
|
||||
color: var(--jio-color-text);
|
||||
transition: color 0.15s;
|
||||
}
|
||||
|
||||
.aksrt-field-group__desc {
|
||||
.jio-field-group__desc {
|
||||
margin: 4px 0 0;
|
||||
font-size: 12px;
|
||||
color: var(--aksrt-color-muted);
|
||||
color: var(--jio-color-muted);
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.aksrt-field-group__actions {
|
||||
.jio-field-group__actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.aksrt-field-group__collapse-btn {
|
||||
.jio-field-group__collapse-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
border: 1px solid var(--aksrt-color-border);
|
||||
background: var(--aksrt-color-canvas);
|
||||
color: var(--aksrt-color-text-secondary);
|
||||
border: 1px solid var(--jio-color-border);
|
||||
background: var(--jio-color-canvas);
|
||||
color: var(--jio-color-text-secondary);
|
||||
border-radius: 999px;
|
||||
padding: 4px 10px;
|
||||
font-size: 12px;
|
||||
@@ -153,25 +153,25 @@ function toggleCollapse() {
|
||||
transition: color 0.15s, border-color 0.15s, background 0.15s;
|
||||
}
|
||||
|
||||
.aksrt-field-group__collapse-btn:hover {
|
||||
color: var(--aksrt-color-primary);
|
||||
border-color: var(--aksrt-color-primary);
|
||||
background: var(--aksrt-color-primary-soft);
|
||||
.jio-field-group__collapse-btn:hover {
|
||||
color: var(--jio-color-primary);
|
||||
border-color: var(--jio-color-primary);
|
||||
background: var(--jio-color-primary-soft);
|
||||
}
|
||||
|
||||
.aksrt-field-group__body {
|
||||
.jio-field-group__body {
|
||||
padding: 16px 20px 20px;
|
||||
}
|
||||
|
||||
/* Collapse transition */
|
||||
.aksrt-collapse-enter-active,
|
||||
.aksrt-collapse-leave-active {
|
||||
.jio-collapse-enter-active,
|
||||
.jio-collapse-leave-active {
|
||||
transition: all 0.2s ease;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.aksrt-collapse-enter-from,
|
||||
.aksrt-collapse-leave-to {
|
||||
.jio-collapse-enter-from,
|
||||
.jio-collapse-leave-to {
|
||||
opacity: 0;
|
||||
max-height: 0;
|
||||
padding-top: 0;
|
||||
+39
-39
@@ -1,15 +1,15 @@
|
||||
<template>
|
||||
<div class="aksrt-form-renderer">
|
||||
<div class="jio-form-renderer">
|
||||
<!-- Page header with title and description -->
|
||||
<div v-if="title" class="aksrt-form-renderer__header">
|
||||
<div class="aksrt-form-renderer__header-left">
|
||||
<FaIcon v-if="icon" :icon="icon" :size="22" class="aksrt-form-renderer__header-icon" />
|
||||
<div v-if="title" class="jio-form-renderer__header">
|
||||
<div class="jio-form-renderer__header-left">
|
||||
<FaIcon v-if="icon" :icon="icon" :size="22" class="jio-form-renderer__header-icon" />
|
||||
<div>
|
||||
<h1 class="aksrt-form-renderer__title">{{ title }}</h1>
|
||||
<p v-if="description" class="aksrt-form-renderer__desc">{{ description }}</p>
|
||||
<h1 class="jio-form-renderer__title">{{ title }}</h1>
|
||||
<p v-if="description" class="jio-form-renderer__desc">{{ description }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="showSearch" class="aksrt-form-renderer__search">
|
||||
<div v-if="showSearch" class="jio-form-renderer__search">
|
||||
<n-input
|
||||
v-model:value="searchQuery"
|
||||
placeholder="搜索设置项..."
|
||||
@@ -24,7 +24,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Loading state -->
|
||||
<div v-if="loading" class="aksrt-form-renderer__loading">
|
||||
<div v-if="loading" class="jio-form-renderer__loading">
|
||||
<n-spin size="medium" />
|
||||
<span>加载中...</span>
|
||||
</div>
|
||||
@@ -45,7 +45,7 @@
|
||||
<!-- Form content -->
|
||||
<template v-else-if="schema && schema.length">
|
||||
<template v-for="group in filteredSchema" :key="groupKey(group)">
|
||||
<AksFieldGroup
|
||||
<JioFieldGroup
|
||||
v-if="isGroupVisible(group)"
|
||||
:title="group.title"
|
||||
:description="group.description"
|
||||
@@ -59,7 +59,7 @@
|
||||
<!-- Tabbed: renders tabs whose sub-fields are flat top-level keys -->
|
||||
<div
|
||||
v-if="field.type === 'tabbed' && (field.tabs || []).length"
|
||||
class="aksrt-form-renderer__tabbed"
|
||||
class="jio-form-renderer__tabbed"
|
||||
>
|
||||
<n-tabs type="line" animated>
|
||||
<n-tab-pane
|
||||
@@ -69,7 +69,7 @@
|
||||
:tab="tabName(tab)"
|
||||
>
|
||||
<template v-for="sub in (tab.fields || [])" :key="sub.key">
|
||||
<AksField
|
||||
<JioField
|
||||
v-if="isFieldVisible(sub)"
|
||||
:schema="sub"
|
||||
:model-value="getFieldValue(sub.key)"
|
||||
@@ -82,7 +82,7 @@
|
||||
</n-tab-pane>
|
||||
</n-tabs>
|
||||
</div>
|
||||
<AksSorterField
|
||||
<JioSorterField
|
||||
v-else-if="field.type === 'custom' && field.renderer === 'sorter'"
|
||||
:model-value="getFieldValue(field.key) || {}"
|
||||
:options="field.options || []"
|
||||
@@ -93,7 +93,7 @@
|
||||
:name="`field-${field.key}`"
|
||||
v-bind="{ field, value: getFieldValue(field.key), setValue: (value) => setFieldValue(field.key, value), formData }"
|
||||
>
|
||||
<AksField
|
||||
<JioField
|
||||
:schema="field"
|
||||
:model-value="getFieldValue(field.key)"
|
||||
:form-data="formData"
|
||||
@@ -102,7 +102,7 @@
|
||||
@action="emit('field-action', $event)"
|
||||
/>
|
||||
</slot>
|
||||
<AksField
|
||||
<JioField
|
||||
v-else
|
||||
:schema="field"
|
||||
:model-value="getFieldValue(field.key)"
|
||||
@@ -117,24 +117,24 @@
|
||||
|
||||
<!-- Custom group content slot -->
|
||||
<slot :name="`group-${group.key}`" v-bind="{ group, formData }" />
|
||||
</AksFieldGroup>
|
||||
</JioFieldGroup>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<!-- Empty state -->
|
||||
<div v-else class="aksrt-form-renderer__empty">
|
||||
<div v-else class="jio-form-renderer__empty">
|
||||
<FaIcon icon="fa fas fa-cog" :size="32" />
|
||||
<p>暂无配置项</p>
|
||||
</div>
|
||||
|
||||
<!-- Sticky save bar -->
|
||||
<AksSaveBar :dirty="dirty" :saving="saving" @save="$emit('save')" @reload="$emit('reload')" />
|
||||
<JioSaveBar :dirty="dirty" :saving="saving" @save="$emit('save')" @reload="$emit('reload')" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
/**
|
||||
* AksFormRenderer — the core schema-driven form engine.
|
||||
* JioFormRenderer — the core schema-driven form engine.
|
||||
*
|
||||
* Takes a schema definition (array of field groups) and a reactive form data object,
|
||||
* and renders a complete settings page with:
|
||||
@@ -146,10 +146,10 @@
|
||||
*/
|
||||
import { ref, computed, useSlots } from 'vue'
|
||||
import FaIcon from './FaIcon.vue'
|
||||
import AksSaveBar from './AksSaveBar.vue'
|
||||
import AksFieldGroup from './AksFieldGroup.vue'
|
||||
import AksField from './AksField.vue'
|
||||
import AksSorterField from './AksSorterField.vue'
|
||||
import JioSaveBar from './JioSaveBar.vue'
|
||||
import JioFieldGroup from './JioFieldGroup.vue'
|
||||
import JioField from './JioField.vue'
|
||||
import JioSorterField from './JioSorterField.vue'
|
||||
|
||||
const props = defineProps({
|
||||
/** Page title */
|
||||
@@ -308,7 +308,7 @@ const filteredSchema = computed(() => {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.aksrt-form-renderer__header {
|
||||
.jio-form-renderer__header {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
@@ -317,73 +317,73 @@ const filteredSchema = computed(() => {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.aksrt-form-renderer__header-left {
|
||||
.jio-form-renderer__header-left {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.aksrt-form-renderer__header-icon {
|
||||
color: var(--aksrt-color-primary);
|
||||
.jio-form-renderer__header-icon {
|
||||
color: var(--jio-color-primary);
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.aksrt-form-renderer__title {
|
||||
.jio-form-renderer__title {
|
||||
margin: 0;
|
||||
font-size: 22px;
|
||||
font-weight: 700;
|
||||
color: var(--aksrt-color-text);
|
||||
color: var(--jio-color-text);
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
|
||||
.aksrt-form-renderer__desc {
|
||||
.jio-form-renderer__desc {
|
||||
margin: 4px 0 0;
|
||||
font-size: 13px;
|
||||
color: var(--aksrt-color-muted);
|
||||
color: var(--jio-color-muted);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.aksrt-form-renderer__tabbed {
|
||||
.jio-form-renderer__tabbed {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.aksrt-form-renderer__search {
|
||||
.jio-form-renderer__search {
|
||||
min-width: 200px;
|
||||
max-width: 280px;
|
||||
}
|
||||
|
||||
.aksrt-form-renderer__loading {
|
||||
.jio-form-renderer__loading {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 60px 0;
|
||||
color: var(--aksrt-color-muted);
|
||||
color: var(--jio-color-muted);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.aksrt-form-renderer__empty {
|
||||
.jio-form-renderer__empty {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 60px 0;
|
||||
color: var(--aksrt-color-border-strong);
|
||||
color: var(--jio-color-border-strong);
|
||||
}
|
||||
|
||||
.aksrt-form-renderer__empty p {
|
||||
.jio-form-renderer__empty p {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* Mobile */
|
||||
@media (max-width: 768px) {
|
||||
.aksrt-form-renderer__header {
|
||||
.jio-form-renderer__header {
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.aksrt-form-renderer__search {
|
||||
.jio-form-renderer__search {
|
||||
max-width: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
+23
-23
@@ -1,23 +1,23 @@
|
||||
<template>
|
||||
<div class="aksrt-gallery-field">
|
||||
<div v-if="attachmentIds.length" class="aksrt-gallery-field__grid">
|
||||
<div class="jio-gallery-field">
|
||||
<div v-if="attachmentIds.length" class="jio-gallery-field__grid">
|
||||
<article
|
||||
v-for="(attachmentId, index) in attachmentIds"
|
||||
:key="attachmentId"
|
||||
class="aksrt-gallery-field__item"
|
||||
class="jio-gallery-field__item"
|
||||
>
|
||||
<img
|
||||
v-if="previews[attachmentId]?.url"
|
||||
:src="previews[attachmentId].url"
|
||||
:alt="previews[attachmentId].alt || `附件 ${attachmentId}`"
|
||||
class="aksrt-gallery-field__image"
|
||||
class="jio-gallery-field__image"
|
||||
/>
|
||||
<div v-else class="aksrt-gallery-field__placeholder">
|
||||
<div v-else class="jio-gallery-field__placeholder">
|
||||
<FaIcon icon="fa fas fa-image" :size="22" />
|
||||
<span>#{{ attachmentId }}</span>
|
||||
</div>
|
||||
|
||||
<div class="aksrt-gallery-field__actions">
|
||||
<div class="jio-gallery-field__actions">
|
||||
<n-button
|
||||
size="tiny"
|
||||
quaternary
|
||||
@@ -49,12 +49,12 @@
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<div v-else class="aksrt-gallery-field__empty">
|
||||
<div v-else class="jio-gallery-field__empty">
|
||||
<FaIcon icon="fa fas fa-images" :size="28" />
|
||||
<span>尚未选择图片</span>
|
||||
</div>
|
||||
|
||||
<div class="aksrt-gallery-field__buttons">
|
||||
<div class="jio-gallery-field__buttons">
|
||||
<n-button type="primary" secondary @click="openMediaLibrary">
|
||||
<FaIcon icon="fa fas fa-plus" />
|
||||
{{ attachmentIds.length ? '编辑图片' : '选择图片' }}
|
||||
@@ -64,7 +64,7 @@
|
||||
</n-button>
|
||||
</div>
|
||||
|
||||
<p v-if="mediaError" class="aksrt-gallery-field__error">{{ mediaError }}</p>
|
||||
<p v-if="mediaError" class="jio-gallery-field__error">{{ mediaError }}</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -172,25 +172,25 @@ watch(attachmentIds, hydratePreviews, { immediate: true })
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.aksrt-gallery-field__grid {
|
||||
.jio-gallery-field__grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.aksrt-gallery-field__item {
|
||||
.jio-gallery-field__item {
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
max-width: 100%;
|
||||
padding: 4px 4px 0;
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--aksrt-color-border);
|
||||
border: 1px solid var(--jio-color-border);
|
||||
border-radius: 8px;
|
||||
background: var(--aksrt-color-canvas);
|
||||
background: var(--jio-color-canvas);
|
||||
}
|
||||
|
||||
.aksrt-gallery-field__image {
|
||||
.jio-gallery-field__image {
|
||||
display: block;
|
||||
width: auto;
|
||||
height: auto;
|
||||
@@ -199,7 +199,7 @@ watch(attachmentIds, hydratePreviews, { immediate: true })
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.aksrt-gallery-field__placeholder {
|
||||
.jio-gallery-field__placeholder {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 90px;
|
||||
@@ -207,35 +207,35 @@ watch(attachmentIds, hydratePreviews, { immediate: true })
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 4px;
|
||||
color: var(--aksrt-color-muted);
|
||||
color: var(--jio-color-muted);
|
||||
}
|
||||
|
||||
.aksrt-gallery-field__actions {
|
||||
.jio-gallery-field__actions {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 4px;
|
||||
border-top: 1px solid var(--aksrt-color-border);
|
||||
border-top: 1px solid var(--jio-color-border);
|
||||
}
|
||||
|
||||
.aksrt-gallery-field__empty {
|
||||
.jio-gallery-field__empty {
|
||||
display: flex;
|
||||
min-height: 116px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
border: 1px dashed var(--aksrt-color-border-strong);
|
||||
border: 1px dashed var(--jio-color-border-strong);
|
||||
border-radius: 8px;
|
||||
color: var(--aksrt-color-muted);
|
||||
color: var(--jio-color-muted);
|
||||
}
|
||||
|
||||
.aksrt-gallery-field__buttons {
|
||||
.jio-gallery-field__buttons {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.aksrt-gallery-field__error {
|
||||
.jio-gallery-field__error {
|
||||
margin: 8px 0 0;
|
||||
color: #d03050;
|
||||
font-size: 12px;
|
||||
+27
-27
@@ -1,21 +1,21 @@
|
||||
<template>
|
||||
<div class="aksrt-image-select" role="radiogroup">
|
||||
<div class="jio-image-select" role="radiogroup">
|
||||
<button
|
||||
v-for="opt in options"
|
||||
:key="opt.value"
|
||||
type="button"
|
||||
role="radio"
|
||||
class="aksrt-image-select__item"
|
||||
class="jio-image-select__item"
|
||||
:class="{ 'is-active': modelValue === opt.value }"
|
||||
:aria-checked="modelValue === opt.value"
|
||||
:disabled="disabled"
|
||||
@click="emit('update:modelValue', opt.value)"
|
||||
>
|
||||
<img v-if="opt.image" :src="opt.image" :alt="opt.label" class="aksrt-image-select__img" :style="imgStyle(opt)" />
|
||||
<span v-else class="aksrt-image-select__placeholder" :style="imgStyle(opt)">
|
||||
<img v-if="opt.image" :src="opt.image" :alt="opt.label" class="jio-image-select__img" :style="imgStyle(opt)" />
|
||||
<span v-else class="jio-image-select__placeholder" :style="imgStyle(opt)">
|
||||
<FaIcon :icon="opt.icon || 'fa fas fa-image'" :size="20" />
|
||||
</span>
|
||||
<span class="aksrt-image-select__label">{{ opt.label }}</span>
|
||||
<span class="jio-image-select__label">{{ opt.label }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
@@ -37,78 +37,78 @@ const emit = defineEmits(['update:modelValue'])
|
||||
// An option may override the group width via its own `width`.
|
||||
function imgStyle(opt) {
|
||||
const width = opt && opt.width ? opt.width : props.previewWidth
|
||||
return { '--aksrt-image-select-max': typeof width === 'number' ? `${width}px` : width }
|
||||
return { '--jio-image-select-max': typeof width === 'number' ? `${width}px` : width }
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.aksrt-image-select {
|
||||
.jio-image-select {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.aksrt-image-select__item {
|
||||
.jio-image-select__item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
min-width: 80px;
|
||||
padding: 8px;
|
||||
border: 2px solid var(--aksrt-color-border);
|
||||
border: 2px solid var(--jio-color-border);
|
||||
border-radius: 8px;
|
||||
background: var(--aksrt-color-surface);
|
||||
background: var(--jio-color-surface);
|
||||
cursor: pointer;
|
||||
transition: border-color .15s ease, background-color .15s ease;
|
||||
}
|
||||
|
||||
.aksrt-image-select__item:hover,
|
||||
.aksrt-image-select__item:focus-visible {
|
||||
.jio-image-select__item:hover,
|
||||
.jio-image-select__item:focus-visible {
|
||||
outline: none;
|
||||
border-color: var(--aksrt-color-primary);
|
||||
border-color: var(--jio-color-primary);
|
||||
}
|
||||
|
||||
.aksrt-image-select__item.is-active {
|
||||
border-color: var(--aksrt-color-primary);
|
||||
background: var(--aksrt-color-primary-soft);
|
||||
.jio-image-select__item.is-active {
|
||||
border-color: var(--jio-color-primary);
|
||||
background: var(--jio-color-primary-soft);
|
||||
}
|
||||
|
||||
.aksrt-image-select__item:disabled {
|
||||
.jio-image-select__item:disabled {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.aksrt-image-select__img {
|
||||
.jio-image-select__img {
|
||||
display: block;
|
||||
/* Preview images are wide layout strips (header 597x57, footer 800x190, slide
|
||||
align 200x101), so let them keep their own aspect ratio inside the card.
|
||||
A fixed square box with object-fit:cover crops the layout to an
|
||||
unrecognisable sliver — the width is capped instead and the height follows. */
|
||||
width: 100%;
|
||||
max-width: var(--aksrt-image-select-max, 140px);
|
||||
max-width: var(--jio-image-select-max, 140px);
|
||||
height: auto;
|
||||
border-radius: 4px;
|
||||
background: var(--aksrt-color-canvas);
|
||||
background: var(--jio-color-canvas);
|
||||
}
|
||||
|
||||
.aksrt-image-select__placeholder {
|
||||
.jio-image-select__placeholder {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: var(--aksrt-image-select-max, 140px);
|
||||
width: var(--jio-image-select-max, 140px);
|
||||
height: 60px;
|
||||
border-radius: 4px;
|
||||
background: var(--aksrt-color-canvas);
|
||||
color: var(--aksrt-color-border-strong);
|
||||
background: var(--jio-color-canvas);
|
||||
color: var(--jio-color-border-strong);
|
||||
}
|
||||
|
||||
.aksrt-image-select__label {
|
||||
color: var(--aksrt-color-text-secondary);
|
||||
.jio-image-select__label {
|
||||
color: var(--jio-color-text-secondary);
|
||||
font-size: 11px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.aksrt-image-select__item {
|
||||
.jio-image-select__item {
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
+27
-27
@@ -1,17 +1,17 @@
|
||||
<template>
|
||||
<div class="aksrt-media-field">
|
||||
<div v-if="modelValue" class="aksrt-media-field__preview">
|
||||
<div class="jio-media-field">
|
||||
<div v-if="modelValue" class="jio-media-field__preview">
|
||||
<img
|
||||
v-if="isImage"
|
||||
:src="modelValue"
|
||||
:alt="placeholder"
|
||||
class="aksrt-media-field__image"
|
||||
class="jio-media-field__image"
|
||||
/>
|
||||
<div v-else class="aksrt-media-field__file">
|
||||
<div v-else class="jio-media-field__file">
|
||||
<FaIcon icon="fa fas fa-file" :size="24" />
|
||||
<span class="aksrt-media-field__filename">{{ fileName }}</span>
|
||||
<span class="jio-media-field__filename">{{ fileName }}</span>
|
||||
</div>
|
||||
<div class="aksrt-media-field__overlay">
|
||||
<div class="jio-media-field__overlay">
|
||||
<n-button size="tiny" secondary circle title="更换" @click="openMediaLibrary">
|
||||
<FaIcon icon="fa fas fa-exchange-alt" />
|
||||
</n-button>
|
||||
@@ -21,7 +21,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else class="aksrt-media-field__empty" @click="openMediaLibrary">
|
||||
<div v-else class="jio-media-field__empty" @click="openMediaLibrary">
|
||||
<FaIcon icon="fa fas fa-cloud-upload-alt" :size="28" />
|
||||
<span>{{ placeholder }}</span>
|
||||
</div>
|
||||
@@ -30,7 +30,7 @@
|
||||
:value="modelValue"
|
||||
:placeholder="'或直接输入 URL'"
|
||||
size="small"
|
||||
class="aksrt-media-field__url"
|
||||
class="jio-media-field__url"
|
||||
@update:value="emit('update:modelValue', $event)"
|
||||
/>
|
||||
</div>
|
||||
@@ -38,7 +38,7 @@
|
||||
|
||||
<script setup>
|
||||
/**
|
||||
* AksMediaField — image/file selector with preview.
|
||||
* JioMediaField — image/file selector with preview.
|
||||
*
|
||||
* In the WordPress admin context, this can open the WP Media Library.
|
||||
* For now, it supports direct URL input with preview.
|
||||
@@ -95,7 +95,7 @@ function clearMedia() {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.aksrt-media-field__preview {
|
||||
.jio-media-field__preview {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
max-width: 100%;
|
||||
@@ -104,13 +104,13 @@ function clearMedia() {
|
||||
padding: 4px;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--aksrt-color-border);
|
||||
background: var(--aksrt-color-canvas);
|
||||
border: 1px solid var(--jio-color-border);
|
||||
background: var(--jio-color-canvas);
|
||||
margin-bottom: 8px;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.aksrt-media-field__image {
|
||||
.jio-media-field__image {
|
||||
display: block;
|
||||
width: auto;
|
||||
height: auto;
|
||||
@@ -119,21 +119,21 @@ function clearMedia() {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.aksrt-media-field__file {
|
||||
.jio-media-field__file {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 16px;
|
||||
background: var(--aksrt-color-canvas);
|
||||
background: var(--jio-color-canvas);
|
||||
}
|
||||
|
||||
.aksrt-media-field__filename {
|
||||
.jio-media-field__filename {
|
||||
font-size: 13px;
|
||||
color: var(--aksrt-color-text-secondary);
|
||||
color: var(--jio-color-text-secondary);
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.aksrt-media-field__overlay {
|
||||
.jio-media-field__overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
@@ -144,33 +144,33 @@ function clearMedia() {
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
.aksrt-media-field__preview:hover .aksrt-media-field__overlay {
|
||||
.jio-media-field__preview:hover .jio-media-field__overlay {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.aksrt-media-field__empty {
|
||||
.jio-media-field__empty {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
padding: 32px;
|
||||
border: 2px dashed var(--aksrt-color-border-strong);
|
||||
border: 2px dashed var(--jio-color-border-strong);
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
color: var(--aksrt-color-muted);
|
||||
color: var(--jio-color-muted);
|
||||
font-size: 13px;
|
||||
transition: all 0.2s;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.aksrt-media-field__empty:hover {
|
||||
border-color: var(--aksrt-color-primary);
|
||||
color: var(--aksrt-color-primary);
|
||||
background: var(--aksrt-color-primary-soft);
|
||||
.jio-media-field__empty:hover {
|
||||
border-color: var(--jio-color-primary);
|
||||
color: var(--jio-color-primary);
|
||||
background: var(--jio-color-primary-soft);
|
||||
}
|
||||
|
||||
.aksrt-media-field__url {
|
||||
.jio-media-field__url {
|
||||
margin-top: 4px;
|
||||
}
|
||||
</style>
|
||||
+23
-23
@@ -1,27 +1,27 @@
|
||||
<template>
|
||||
<div class="aksrt-repeater-list">
|
||||
<div class="jio-repeater-list">
|
||||
<draggable
|
||||
v-if="rows.length"
|
||||
v-model="rows"
|
||||
v-bind="SORTABLE_LIST_ATTRS"
|
||||
:item-key="rowKey"
|
||||
:disabled="schema.disabled"
|
||||
class="aksrt-sortable"
|
||||
class="jio-sortable"
|
||||
>
|
||||
<template #item="{ element, index }">
|
||||
<div class="aksrt-sortable__item">
|
||||
<div class="aksrt-sortable__head">
|
||||
<AksSortHandle />
|
||||
<span class="aksrt-sortable__icon" :class="preview(element).iconClass">
|
||||
<span v-if="preview(element).customIcon" class="aksrt-repeater-list__icon-html" v-html="preview(element).customIcon" />
|
||||
<div class="jio-sortable__item">
|
||||
<div class="jio-sortable__head">
|
||||
<JioSortHandle />
|
||||
<span class="jio-sortable__icon" :class="preview(element).iconClass">
|
||||
<span v-if="preview(element).customIcon" class="jio-repeater-list__icon-html" v-html="preview(element).customIcon" />
|
||||
<i v-else-if="preview(element).icon" :class="preview(element).icon" aria-hidden="true" />
|
||||
<FaIcon v-else icon="fa fas fa-star" :size="16" />
|
||||
</span>
|
||||
<div class="aksrt-sortable__main">
|
||||
<span class="aksrt-sortable__title">{{ preview(element).title }}</span>
|
||||
<span class="aksrt-sortable__desc">{{ preview(element).desc }}</span>
|
||||
<div class="jio-sortable__main">
|
||||
<span class="jio-sortable__title">{{ preview(element).title }}</span>
|
||||
<span class="jio-sortable__desc">{{ preview(element).desc }}</span>
|
||||
</div>
|
||||
<div class="aksrt-sortable__actions">
|
||||
<div class="jio-sortable__actions">
|
||||
<n-button size="tiny" quaternary type="primary" :disabled="schema.disabled" @click="openModal(index)">
|
||||
<template #icon><FaIcon icon="fa fas fa-edit" :size="12" /></template>
|
||||
编辑
|
||||
@@ -35,7 +35,7 @@
|
||||
</div>
|
||||
</template>
|
||||
</draggable>
|
||||
<div v-else class="aksrt-sortable__empty">
|
||||
<div v-else class="jio-sortable__empty">
|
||||
<FaIcon icon="fa fas fa-inbox" :size="18" />
|
||||
<span>{{ schema.emptyText || '暂无数据' }}</span>
|
||||
</div>
|
||||
@@ -50,11 +50,11 @@
|
||||
:mask-closable="false"
|
||||
preset="card"
|
||||
:title="editingIndex >= 0 ? (schema.editText || '编辑项目') : (schema.addText || '添加项目')"
|
||||
class="aksrt-modal-card"
|
||||
class="jio-modal-card"
|
||||
style="max-width: 640px; width: 92vw;"
|
||||
>
|
||||
<div class="aksrt-repeater-list__modal-fields">
|
||||
<AksField
|
||||
<div class="jio-repeater-list__modal-fields">
|
||||
<JioField
|
||||
v-for="field in draftFields"
|
||||
:key="field.key"
|
||||
:schema="field"
|
||||
@@ -63,7 +63,7 @@
|
||||
@update:model-value="draft = { ...draft, [field.key]: $event }"
|
||||
/>
|
||||
</div>
|
||||
<div class="aksrt-modal-footer">
|
||||
<div class="jio-modal-footer">
|
||||
<n-button @click="closeModal">取消</n-button>
|
||||
<n-button type="primary" @click="confirmModal">确定</n-button>
|
||||
</div>
|
||||
@@ -76,8 +76,8 @@
|
||||
import { computed, ref } from 'vue'
|
||||
import draggable from 'vuedraggable'
|
||||
import FaIcon from './FaIcon.vue'
|
||||
import AksField from './AksField.vue'
|
||||
import AksSortHandle from './AksSortHandle.vue'
|
||||
import JioField from './JioField.vue'
|
||||
import JioSortHandle from './JioSortHandle.vue'
|
||||
import { matchDependency } from './field-dependency.js'
|
||||
import { SORTABLE_LIST_ATTRS } from './sortable-options.js'
|
||||
import { createRepeaterRow, createRepeaterRowId, repeaterRowPreview } from './repeater-rows.js'
|
||||
@@ -151,13 +151,13 @@ function confirmModal() {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.aksrt-repeater-list {
|
||||
.jio-repeater-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.aksrt-repeater-list__icon-html {
|
||||
.jio-repeater-list__icon-html {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -165,14 +165,14 @@ function confirmModal() {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.aksrt-repeater-list__icon-html :deep(svg),
|
||||
.aksrt-repeater-list__icon-html :deep(img) {
|
||||
.jio-repeater-list__icon-html :deep(svg),
|
||||
.jio-repeater-list__icon-html :deep(img) {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.aksrt-repeater-list__modal-fields {
|
||||
.jio-repeater-list__modal-fields {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
+16
-16
@@ -1,11 +1,11 @@
|
||||
<template>
|
||||
<div v-if="dirty" class="aksrt-form-renderer__save-bar">
|
||||
<div class="aksrt-form-renderer__save-bar__inner">
|
||||
<div class="aksrt-form-renderer__save-bar__info">
|
||||
<div v-if="dirty" class="jio-form-renderer__save-bar">
|
||||
<div class="jio-form-renderer__save-bar__inner">
|
||||
<div class="jio-form-renderer__save-bar__info">
|
||||
<FaIcon icon="fa fas fa-exclamation-circle" :size="14" />
|
||||
<span>您有未保存的更改</span>
|
||||
</div>
|
||||
<div class="aksrt-form-renderer__save-bar__actions">
|
||||
<div class="jio-form-renderer__save-bar__actions">
|
||||
<n-button size="small" :disabled="saving" @click="emit('reload')">放弃更改</n-button>
|
||||
<n-button
|
||||
size="small"
|
||||
@@ -23,8 +23,8 @@
|
||||
|
||||
<script setup>
|
||||
/**
|
||||
* AksSaveBar — the admin-wide sticky save bar (shown only when dirty).
|
||||
* Shared by AksFormRenderer (schema pages) and custom settings pages.
|
||||
* JioSaveBar — the admin-wide sticky save bar (shown only when dirty).
|
||||
* Shared by JioFormRenderer (schema pages) and custom settings pages.
|
||||
*/
|
||||
import FaIcon from './FaIcon.vue'
|
||||
|
||||
@@ -37,19 +37,19 @@ const emit = defineEmits(['save', 'reload'])
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.aksrt-form-renderer__save-bar {
|
||||
.jio-form-renderer__save-bar {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 1000;
|
||||
background: var(--aksrt-color-surface);
|
||||
border-top: 1px solid var(--aksrt-color-border);
|
||||
box-shadow: var(--aksrt-shadow-floating);
|
||||
background: var(--jio-color-surface);
|
||||
border-top: 1px solid var(--jio-color-border);
|
||||
box-shadow: var(--jio-shadow-floating);
|
||||
animation: slideUp 0.2s ease;
|
||||
}
|
||||
|
||||
.aksrt-form-renderer__save-bar__inner {
|
||||
.jio-form-renderer__save-bar__inner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
@@ -59,7 +59,7 @@ const emit = defineEmits(['save', 'reload'])
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.aksrt-form-renderer__save-bar__info {
|
||||
.jio-form-renderer__save-bar__info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
@@ -67,7 +67,7 @@ const emit = defineEmits(['save', 'reload'])
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.aksrt-form-renderer__save-bar__actions {
|
||||
.jio-form-renderer__save-bar__actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
@@ -84,17 +84,17 @@ const emit = defineEmits(['save', 'reload'])
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.aksrt-form-renderer__save-bar__inner {
|
||||
.jio-form-renderer__save-bar__inner {
|
||||
flex-direction: column;
|
||||
padding: 10px 14px;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.aksrt-form-renderer__save-bar__actions {
|
||||
.jio-form-renderer__save-bar__actions {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.aksrt-form-renderer__save-bar__actions .n-button {
|
||||
.jio-form-renderer__save-bar__actions .n-button {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
+5
-5
@@ -1,9 +1,9 @@
|
||||
<template>
|
||||
<div v-if="visible" class="aksrt-secret-clear">
|
||||
<div v-if="visible" class="jio-secret-clear">
|
||||
<n-button size="small" secondary :type="marked ? 'warning' : 'error'" @click="$emit('toggle')">
|
||||
{{ marked ? '取消清除' : '清除已配置密钥' }}
|
||||
</n-button>
|
||||
<span v-if="marked" class="aksrt-secret-clear__hint">保存后将删除此密钥</span>
|
||||
<span v-if="marked" class="jio-secret-clear__hint">保存后将删除此密钥</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -22,15 +22,15 @@ defineEmits(['toggle'])
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.aksrt-secret-clear {
|
||||
.jio-secret-clear {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.aksrt-secret-clear__hint {
|
||||
color: var(--aksrt-color-warning, #d97706);
|
||||
.jio-secret-clear__hint {
|
||||
color: var(--jio-color-warning, #d97706);
|
||||
font-size: 12px;
|
||||
}
|
||||
</style>
|
||||
+8
-8
@@ -4,14 +4,14 @@
|
||||
v-bind="SORTABLE_LIST_ATTRS"
|
||||
:item-key="valueKey"
|
||||
:disabled="disabled"
|
||||
class="aksrt-sortable aksrt-sortable--compact aksrt-selected-order"
|
||||
class="jio-sortable jio-sortable--compact jio-selected-order"
|
||||
>
|
||||
<template #item="{ element, index }">
|
||||
<div class="aksrt-sortable__item">
|
||||
<div class="aksrt-sortable__head">
|
||||
<AksSortHandle />
|
||||
<span class="aksrt-sortable__index">{{ index + 1 }}</span>
|
||||
<span class="aksrt-sortable__title">{{ labelOf(element) }}</span>
|
||||
<div class="jio-sortable__item">
|
||||
<div class="jio-sortable__head">
|
||||
<JioSortHandle />
|
||||
<span class="jio-sortable__index">{{ index + 1 }}</span>
|
||||
<span class="jio-sortable__title">{{ labelOf(element) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -22,7 +22,7 @@
|
||||
/** 多选 + sortable 的已选值排序列表(select / async_select 下方)。 */
|
||||
import { computed } from 'vue'
|
||||
import draggable from 'vuedraggable'
|
||||
import AksSortHandle from './AksSortHandle.vue'
|
||||
import JioSortHandle from './JioSortHandle.vue'
|
||||
import { SORTABLE_LIST_ATTRS } from './sortable-options.js'
|
||||
|
||||
const props = defineProps({
|
||||
@@ -47,7 +47,7 @@ function labelOf(value) {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.aksrt-selected-order {
|
||||
.jio-selected-order {
|
||||
margin-top: 8px;
|
||||
}
|
||||
</style>
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<button type="button" class="aksrt-sortable__handle" :title="label" :aria-label="label">
|
||||
<button type="button" class="jio-sortable__handle" :title="label" :aria-label="label">
|
||||
<i class="fa fas fa-grip-vertical" aria-hidden="true"></i>
|
||||
</button>
|
||||
</template>
|
||||
+24
-24
@@ -1,12 +1,12 @@
|
||||
<template>
|
||||
<div class="aksrt-sorter">
|
||||
<div class="jio-sorter">
|
||||
<div
|
||||
v-for="column in COLUMNS"
|
||||
:key="column.key"
|
||||
class="aksrt-sorter__column"
|
||||
:class="`aksrt-sorter__column--${column.key}`"
|
||||
class="jio-sorter__column"
|
||||
:class="`jio-sorter__column--${column.key}`"
|
||||
>
|
||||
<div class="aksrt-sorter__title">
|
||||
<div class="jio-sorter__title">
|
||||
<span>{{ column.title }}</span>
|
||||
<n-tag size="small" :type="column.tagType" :bordered="false">{{ columns[column.key].length }}</n-tag>
|
||||
</div>
|
||||
@@ -14,21 +14,21 @@
|
||||
v-model="columns[column.key]"
|
||||
v-bind="SORTABLE_LIST_ATTRS"
|
||||
item-key="value"
|
||||
group="aksrt-sorter"
|
||||
class="aksrt-sortable aksrt-sortable--compact aksrt-sorter__list"
|
||||
group="jio-sorter"
|
||||
class="jio-sortable jio-sortable--compact jio-sorter__list"
|
||||
@change="commit"
|
||||
>
|
||||
<template #item="{ element }">
|
||||
<div class="aksrt-sortable__item">
|
||||
<div class="aksrt-sortable__head">
|
||||
<AksSortHandle />
|
||||
<i v-if="element.icon" :class="element.icon" class="aksrt-sorter__icon" aria-hidden="true"></i>
|
||||
<span class="aksrt-sortable__title">{{ element.label }}</span>
|
||||
<div class="jio-sortable__item">
|
||||
<div class="jio-sortable__head">
|
||||
<JioSortHandle />
|
||||
<i v-if="element.icon" :class="element.icon" class="jio-sorter__icon" aria-hidden="true"></i>
|
||||
<span class="jio-sortable__title">{{ element.label }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</draggable>
|
||||
<div v-if="columns[column.key].length === 0" class="aksrt-sortable__empty aksrt-sorter__empty">
|
||||
<div v-if="columns[column.key].length === 0" class="jio-sortable__empty jio-sorter__empty">
|
||||
{{ column.empty }}
|
||||
</div>
|
||||
</div>
|
||||
@@ -39,7 +39,7 @@
|
||||
/** 启用 / 未启用两列拖拽排序器;样式见 styles/sortable-list.css。 */
|
||||
import { reactive, watch } from 'vue'
|
||||
import draggable from 'vuedraggable'
|
||||
import AksSortHandle from './AksSortHandle.vue'
|
||||
import JioSortHandle from './JioSortHandle.vue'
|
||||
import { SORTABLE_LIST_ATTRS } from './sortable-options.js'
|
||||
|
||||
const COLUMNS = [
|
||||
@@ -89,37 +89,37 @@ watch(() => [props.modelValue, props.options], syncColumns, { immediate: true, d
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.aksrt-sorter {
|
||||
.jio-sorter {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.aksrt-sorter__column {
|
||||
.jio-sorter__column {
|
||||
position: relative;
|
||||
min-height: 210px;
|
||||
padding: 12px;
|
||||
border: 1px solid var(--aksrt-color-border);
|
||||
border: 1px solid var(--jio-color-border);
|
||||
border-radius: 8px;
|
||||
background: var(--aksrt-color-canvas);
|
||||
background: var(--jio-color-canvas);
|
||||
}
|
||||
|
||||
.aksrt-sorter__title {
|
||||
.jio-sorter__title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 10px;
|
||||
color: var(--aksrt-color-text);
|
||||
color: var(--jio-color-text);
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* 空列也要留出可放置区域,拖拽目标不能塌成 0 高度。 */
|
||||
.aksrt-sorter__list {
|
||||
.jio-sorter__list {
|
||||
min-height: 44px;
|
||||
}
|
||||
|
||||
.aksrt-sorter__empty {
|
||||
.jio-sorter__empty {
|
||||
position: absolute;
|
||||
right: 12px;
|
||||
bottom: 12px;
|
||||
@@ -128,15 +128,15 @@ watch(() => [props.modelValue, props.options], syncColumns, { immediate: true, d
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.aksrt-sorter__icon {
|
||||
.jio-sorter__icon {
|
||||
flex: 0 0 auto;
|
||||
width: 16px;
|
||||
color: var(--aksrt-color-primary);
|
||||
color: var(--jio-color-primary);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media (max-width: 720px) {
|
||||
.aksrt-sorter {
|
||||
.jio-sorter {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,15 @@
|
||||
/**
|
||||
* 所有后台 vuedraggable 列表共用的 Sortable 选项(v-bind 到 <draggable>)。
|
||||
* 配套样式见 src/styles/sortable-list.css,拖拽手柄统一用 AksSortHandle。
|
||||
* 配套样式见 src/styles/sortable-list.css,拖拽手柄统一用 JioSortHandle。
|
||||
*/
|
||||
const prefersReducedMotion = typeof window !== 'undefined'
|
||||
&& typeof window.matchMedia === 'function'
|
||||
&& window.matchMedia('(prefers-reduced-motion: reduce)').matches
|
||||
|
||||
export const SORTABLE_LIST_ATTRS = Object.freeze({
|
||||
handle: '.aksrt-sortable__handle',
|
||||
ghostClass: 'aksrt-sortable__ghost',
|
||||
chosenClass: 'aksrt-sortable__chosen',
|
||||
dragClass: 'aksrt-sortable__drag',
|
||||
handle: '.jio-sortable__handle',
|
||||
ghostClass: 'jio-sortable__ghost',
|
||||
chosenClass: 'jio-sortable__chosen',
|
||||
dragClass: 'jio-sortable__drag',
|
||||
animation: prefersReducedMotion ? 0 : 180,
|
||||
})
|
||||
@@ -2,7 +2,7 @@ import { onMounted, ref } from 'vue'
|
||||
import api from '@/api'
|
||||
|
||||
/**
|
||||
* AksField 的远程选项加载:async_select / taxonomy_select / user_select / page_select。
|
||||
* JioField 的远程选项加载:async_select / taxonomy_select / user_select / page_select。
|
||||
* 仅按 schema.type 在挂载时拉取对应数据源。
|
||||
*/
|
||||
export function useFieldRemoteOptions(props) {
|
||||
@@ -47,7 +47,7 @@ export function useFieldRemoteOptions(props) {
|
||||
value: t.term_id,
|
||||
}))
|
||||
} catch (e) {
|
||||
console.warn('[AksField] taxonomy load failed:', e)
|
||||
console.warn('[JioField] taxonomy load failed:', e)
|
||||
taxonomyOptions.value = []
|
||||
} finally {
|
||||
taxonomyLoading.value = false
|
||||
@@ -64,7 +64,7 @@ export function useFieldRemoteOptions(props) {
|
||||
value: u.id,
|
||||
}))
|
||||
} catch (e) {
|
||||
console.warn('[AksField] user load failed:', e)
|
||||
console.warn('[JioField] user load failed:', e)
|
||||
userOptions.value = []
|
||||
} finally {
|
||||
userLoading.value = false
|
||||
@@ -84,7 +84,7 @@ export function useFieldRemoteOptions(props) {
|
||||
const items = res?.data || res || []
|
||||
pageOptions.value = Array.isArray(items) ? items : []
|
||||
} catch (e) {
|
||||
console.warn('[AksField] page load failed:', e)
|
||||
console.warn('[JioField] page load failed:', e)
|
||||
pageOptions.value = []
|
||||
} finally {
|
||||
pageLoading.value = false
|
||||
|
||||
Reference in new issue
Block a user