mirror of https://github.com/halo-dev/halo
feat: add supports for automatic slug generation (halo-dev/console#831)
#### What type of PR is this? /kind feature /milestone 2.2.x #### What this PR does / why we need it: 文章、独立页面、分类、标签支持自动生成别名。 策略: 1. 仅在创建时会自动根据标题或者名称自动生成别名,编辑时如果需要重新生成,可以点击输入框右侧的按钮。 2. 中文会被转为拼音并用 - 隔开,需要注意多音字的情况,目前无法保证多音字是否符合预期。 3. 使用了 https://www.npmjs.com/package/transliteration 库。 #### Which issue(s) this PR fixes: Fixes https://github.com/halo-dev/halo/issues/3171 #### Screenshots: <img width="782" alt="image" src="https://user-images.githubusercontent.com/21301288/213849446-18d70974-7b2c-420c-bd50-93d2c3193895.png"> #### Special notes for your reviewer: 测试方式: 1. 需要 `pnpm install` 4. 测试文章、独立页面、分类、标签创建时,是否可以自动设置别名。 5. 测试别名输入框右侧的生成别名按钮是否正常工作。 6. 测试分类和标签选择器创建新分类或标签时,是否正确设置了别名。 #### Does this PR introduce a user-facing change? ```release-note Console 端的文章、独立页面、分类、标签支持自动生成别名 ```pull/3445/head
parent
82db7d0afd
commit
241ad3cc2f
|
@ -74,6 +74,7 @@
|
|||
"pinia": "^2.0.26",
|
||||
"pretty-bytes": "^6.0.0",
|
||||
"qs": "^6.11.0",
|
||||
"transliteration": "^2.3.5",
|
||||
"vue": "^3.2.45",
|
||||
"vue-grid-layout": "3.0.0-beta1",
|
||||
"vue-router": "^4.1.6",
|
||||
|
|
|
@ -85,6 +85,7 @@ importers:
|
|||
tailwindcss: ^3.2.4
|
||||
tailwindcss-safe-area: ^0.2.2
|
||||
tailwindcss-themer: ^2.0.2
|
||||
transliteration: ^2.3.5
|
||||
typescript: ~4.7.4
|
||||
unplugin-icons: ^0.14.14
|
||||
vite: ^4.0.4
|
||||
|
@ -141,6 +142,7 @@ importers:
|
|||
pinia: 2.0.26_e7lp6ggkpgyi5vqd44m2kxvk6i
|
||||
pretty-bytes: 6.0.0
|
||||
qs: 6.11.0
|
||||
transliteration: 2.3.5
|
||||
vue: 3.2.45
|
||||
vue-grid-layout: 3.0.0-beta1
|
||||
vue-router: 4.1.6_vue@3.2.45
|
||||
|
@ -4267,7 +4269,6 @@ packages:
|
|||
/ansi-regex/5.0.1:
|
||||
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
|
||||
engines: {node: '>=8'}
|
||||
dev: true
|
||||
|
||||
/ansi-regex/6.0.1:
|
||||
resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==}
|
||||
|
@ -4286,7 +4287,6 @@ packages:
|
|||
engines: {node: '>=8'}
|
||||
dependencies:
|
||||
color-convert: 2.0.1
|
||||
dev: true
|
||||
|
||||
/ansi-styles/6.2.1:
|
||||
resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==}
|
||||
|
@ -4783,7 +4783,6 @@ packages:
|
|||
string-width: 4.2.3
|
||||
strip-ansi: 6.0.1
|
||||
wrap-ansi: 7.0.0
|
||||
dev: true
|
||||
|
||||
/clone/1.0.4:
|
||||
resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==}
|
||||
|
@ -4817,7 +4816,6 @@ packages:
|
|||
engines: {node: '>=7.0.0'}
|
||||
dependencies:
|
||||
color-name: 1.1.4
|
||||
dev: true
|
||||
|
||||
/color-name/1.1.3:
|
||||
resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==}
|
||||
|
@ -5358,7 +5356,6 @@ packages:
|
|||
|
||||
/emoji-regex/8.0.0:
|
||||
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
|
||||
dev: true
|
||||
|
||||
/emoji-regex/9.2.2:
|
||||
resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
|
||||
|
@ -5693,7 +5690,6 @@ packages:
|
|||
/escalade/3.1.1:
|
||||
resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==}
|
||||
engines: {node: '>=6'}
|
||||
dev: true
|
||||
|
||||
/escape-html/1.0.3:
|
||||
resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==}
|
||||
|
@ -6296,7 +6292,6 @@ packages:
|
|||
/get-caller-file/2.0.5:
|
||||
resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
|
||||
engines: {node: 6.* || 8.* || >= 10.*}
|
||||
dev: true
|
||||
|
||||
/get-func-name/2.0.0:
|
||||
resolution: {integrity: sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==}
|
||||
|
@ -6802,7 +6797,6 @@ packages:
|
|||
/is-fullwidth-code-point/3.0.0:
|
||||
resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
|
||||
engines: {node: '>=8'}
|
||||
dev: true
|
||||
|
||||
/is-fullwidth-code-point/4.0.0:
|
||||
resolution: {integrity: sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==}
|
||||
|
@ -8447,7 +8441,6 @@ packages:
|
|||
/require-directory/2.1.1:
|
||||
resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
dev: true
|
||||
|
||||
/require-from-string/2.0.2:
|
||||
resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
|
||||
|
@ -8894,7 +8887,6 @@ packages:
|
|||
emoji-regex: 8.0.0
|
||||
is-fullwidth-code-point: 3.0.0
|
||||
strip-ansi: 6.0.1
|
||||
dev: true
|
||||
|
||||
/string-width/5.1.2:
|
||||
resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==}
|
||||
|
@ -8954,7 +8946,6 @@ packages:
|
|||
engines: {node: '>=8'}
|
||||
dependencies:
|
||||
ansi-regex: 5.0.1
|
||||
dev: true
|
||||
|
||||
/strip-ansi/7.0.1:
|
||||
resolution: {integrity: sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==}
|
||||
|
@ -9264,6 +9255,14 @@ packages:
|
|||
punycode: 2.1.1
|
||||
dev: true
|
||||
|
||||
/transliteration/2.3.5:
|
||||
resolution: {integrity: sha512-HAGI4Lq4Q9dZ3Utu2phaWgtm3vB6PkLUFqWAScg/UW+1eZ/Tg6Exo4oC0/3VUol/w4BlefLhUUSVBr/9/ZGQOw==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
yargs: 17.5.1
|
||||
dev: false
|
||||
|
||||
/trim-newlines/3.0.1:
|
||||
resolution: {integrity: sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==}
|
||||
engines: {node: '>=8'}
|
||||
|
@ -10267,7 +10266,6 @@ packages:
|
|||
ansi-styles: 4.3.0
|
||||
string-width: 4.2.3
|
||||
strip-ansi: 6.0.1
|
||||
dev: true
|
||||
|
||||
/wrappy/1.0.2:
|
||||
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
|
||||
|
@ -10306,7 +10304,6 @@ packages:
|
|||
/y18n/5.0.8:
|
||||
resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
|
||||
engines: {node: '>=10'}
|
||||
dev: true
|
||||
|
||||
/yallist/2.1.2:
|
||||
resolution: {integrity: sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==}
|
||||
|
@ -10345,7 +10342,6 @@ packages:
|
|||
/yargs-parser/21.1.1:
|
||||
resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
|
||||
engines: {node: '>=12'}
|
||||
dev: true
|
||||
|
||||
/yargs/15.4.1:
|
||||
resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==}
|
||||
|
@ -10388,7 +10384,6 @@ packages:
|
|||
string-width: 4.2.3
|
||||
y18n: 5.0.8
|
||||
yargs-parser: 21.1.1
|
||||
dev: true
|
||||
|
||||
/yauzl/2.10.0:
|
||||
resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==}
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
import { slugify } from "transliteration";
|
||||
import { watch, type Ref } from "vue";
|
||||
export default function useSlugify(
|
||||
source: Ref<string>,
|
||||
target: Ref<string>,
|
||||
auto: Ref<boolean>
|
||||
) {
|
||||
watch(
|
||||
() => source.value,
|
||||
() => {
|
||||
if (auto.value) {
|
||||
handleGenerateSlug();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const handleGenerateSlug = () => {
|
||||
target.value = slugify(source.value, {
|
||||
trim: true,
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
handleGenerateSlug,
|
||||
};
|
||||
}
|
|
@ -13,6 +13,7 @@ import CategoryTag from "./components/CategoryTag.vue";
|
|||
import SearchResultListItem from "./components/SearchResultListItem.vue";
|
||||
import { apiClient } from "@/utils/api-client";
|
||||
import { usePermission } from "@/utils/permission";
|
||||
import { slugify } from "transliteration";
|
||||
|
||||
const { currentUserHasPermission } = usePermission();
|
||||
|
||||
|
@ -212,7 +213,7 @@ const handleCreateCategory = async () => {
|
|||
category: {
|
||||
spec: {
|
||||
displayName: text.value,
|
||||
slug: text.value,
|
||||
slug: slugify(text.value, { trim: true }),
|
||||
description: "",
|
||||
cover: "",
|
||||
template: "",
|
||||
|
|
|
@ -12,6 +12,7 @@ import {
|
|||
import { onClickOutside } from "@vueuse/core";
|
||||
import Fuse from "fuse.js";
|
||||
import { usePermission } from "@/utils/permission";
|
||||
import { slugify } from "transliteration";
|
||||
|
||||
const { currentUserHasPermission } = usePermission();
|
||||
|
||||
|
@ -196,7 +197,7 @@ const handleCreateTag = async () => {
|
|||
tag: {
|
||||
spec: {
|
||||
displayName: text.value,
|
||||
slug: text.value,
|
||||
slug: slugify(text.value, { trim: true }),
|
||||
color: "#ffffff",
|
||||
cover: "",
|
||||
},
|
||||
|
|
|
@ -1,5 +1,11 @@
|
|||
<script lang="ts" setup>
|
||||
import { Toast, VButton, VModal, VSpace } from "@halo-dev/components";
|
||||
import {
|
||||
IconRefreshLine,
|
||||
Toast,
|
||||
VButton,
|
||||
VModal,
|
||||
VSpace,
|
||||
} from "@halo-dev/components";
|
||||
import { computed, nextTick, ref, watchEffect } from "vue";
|
||||
import type { SinglePage } from "@halo-dev/api-client";
|
||||
import cloneDeep from "lodash.clonedeep";
|
||||
|
@ -10,6 +16,7 @@ import { randomUUID } from "@/utils/id";
|
|||
import { toDatetimeLocal, toISOString } from "@/utils/date";
|
||||
import { submitForm } from "@formkit/core";
|
||||
import AnnotationsForm from "@/components/form/AnnotationsForm.vue";
|
||||
import useSlugify from "@/composables/use-slugify";
|
||||
|
||||
const initialFormState: SinglePage = {
|
||||
spec: {
|
||||
|
@ -258,6 +265,20 @@ const publishTime = computed(() => {
|
|||
const onPublishTimeChange = (value: string) => {
|
||||
formState.value.spec.publishTime = value ? toISOString(value) : undefined;
|
||||
};
|
||||
|
||||
// slug
|
||||
const { handleGenerateSlug } = useSlugify(
|
||||
computed(() => formState.value.spec.title),
|
||||
computed({
|
||||
get() {
|
||||
return formState.value.spec.slug;
|
||||
},
|
||||
set(value) {
|
||||
formState.value.spec.slug = value;
|
||||
},
|
||||
}),
|
||||
computed(() => !isUpdateMode.value)
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -302,7 +323,20 @@ const onPublishTimeChange = (value: string) => {
|
|||
name="slug"
|
||||
type="text"
|
||||
validation="required|length:0,100"
|
||||
></FormKit>
|
||||
help="通常用于生成页面的固定链接"
|
||||
>
|
||||
<template #suffix>
|
||||
<div
|
||||
v-tooltip="'根据标题重新生成别名'"
|
||||
class="group flex h-full cursor-pointer items-center border-l px-3 transition-all hover:bg-gray-100"
|
||||
@click="handleGenerateSlug"
|
||||
>
|
||||
<IconRefreshLine
|
||||
class="h-4 w-4 text-gray-500 group-hover:text-gray-700"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</FormKit>
|
||||
<FormKit
|
||||
v-model="formState.spec.excerpt.autoGenerate"
|
||||
:options="[
|
||||
|
|
|
@ -4,7 +4,13 @@ import { computed, nextTick, ref, watch } from "vue";
|
|||
import { apiClient } from "@/utils/api-client";
|
||||
|
||||
// components
|
||||
import { Toast, VButton, VModal, VSpace } from "@halo-dev/components";
|
||||
import {
|
||||
IconRefreshLine,
|
||||
Toast,
|
||||
VButton,
|
||||
VModal,
|
||||
VSpace,
|
||||
} from "@halo-dev/components";
|
||||
import SubmitButton from "@/components/button/SubmitButton.vue";
|
||||
|
||||
// types
|
||||
|
@ -16,6 +22,7 @@ import { reset } from "@formkit/core";
|
|||
import { setFocus } from "@/formkit/utils/focus";
|
||||
import { useThemeCustomTemplates } from "@/modules/interface/themes/composables/use-theme";
|
||||
import AnnotationsForm from "@/components/form/AnnotationsForm.vue";
|
||||
import useSlugify from "@/composables/use-slugify";
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
|
@ -138,6 +145,20 @@ watch(
|
|||
|
||||
// custom templates
|
||||
const { templates } = useThemeCustomTemplates("category");
|
||||
|
||||
// slug
|
||||
const { handleGenerateSlug } = useSlugify(
|
||||
computed(() => formState.value.spec.displayName),
|
||||
computed({
|
||||
get() {
|
||||
return formState.value.spec.slug;
|
||||
},
|
||||
set(value) {
|
||||
formState.value.spec.slug = value;
|
||||
},
|
||||
}),
|
||||
computed(() => !isUpdateMode.value)
|
||||
);
|
||||
</script>
|
||||
<template>
|
||||
<VModal
|
||||
|
@ -171,12 +192,24 @@ const { templates } = useThemeCustomTemplates("category");
|
|||
></FormKit>
|
||||
<FormKit
|
||||
v-model="formState.spec.slug"
|
||||
help="通常作为分类访问地址标识"
|
||||
help="通常用于生成分类的固定链接"
|
||||
name="slug"
|
||||
label="别名"
|
||||
type="text"
|
||||
validation="required|length:0,50"
|
||||
></FormKit>
|
||||
>
|
||||
<template #suffix>
|
||||
<div
|
||||
v-tooltip="'根据名称重新生成别名'"
|
||||
class="group flex h-full cursor-pointer items-center border-l px-3 transition-all hover:bg-gray-100"
|
||||
@click="handleGenerateSlug"
|
||||
>
|
||||
<IconRefreshLine
|
||||
class="h-4 w-4 text-gray-500 group-hover:text-gray-700"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</FormKit>
|
||||
<FormKit
|
||||
v-model="formState.spec.template"
|
||||
:options="templates"
|
||||
|
|
|
@ -1,5 +1,11 @@
|
|||
<script lang="ts" setup>
|
||||
import { Toast, VButton, VModal, VSpace } from "@halo-dev/components";
|
||||
import {
|
||||
IconRefreshLine,
|
||||
Toast,
|
||||
VButton,
|
||||
VModal,
|
||||
VSpace,
|
||||
} from "@halo-dev/components";
|
||||
import { computed, nextTick, ref, watchEffect } from "vue";
|
||||
import type { Post } from "@halo-dev/api-client";
|
||||
import cloneDeep from "lodash.clonedeep";
|
||||
|
@ -10,6 +16,7 @@ import { randomUUID } from "@/utils/id";
|
|||
import { toDatetimeLocal, toISOString } from "@/utils/date";
|
||||
import AnnotationsForm from "@/components/form/AnnotationsForm.vue";
|
||||
import { submitForm } from "@formkit/core";
|
||||
import useSlugify from "@/composables/use-slugify";
|
||||
|
||||
const initialFormState: Post = {
|
||||
spec: {
|
||||
|
@ -222,6 +229,20 @@ const onPublishTimeChange = (value: string) => {
|
|||
};
|
||||
|
||||
const annotationsFormRef = ref<InstanceType<typeof AnnotationsForm>>();
|
||||
|
||||
// slug
|
||||
const { handleGenerateSlug } = useSlugify(
|
||||
computed(() => formState.value.spec.title),
|
||||
computed({
|
||||
get() {
|
||||
return formState.value.spec.slug;
|
||||
},
|
||||
set(value) {
|
||||
formState.value.spec.slug = value;
|
||||
},
|
||||
}),
|
||||
computed(() => !isUpdateMode.value)
|
||||
);
|
||||
</script>
|
||||
<template>
|
||||
<VModal
|
||||
|
@ -265,7 +286,20 @@ const annotationsFormRef = ref<InstanceType<typeof AnnotationsForm>>();
|
|||
name="slug"
|
||||
type="text"
|
||||
validation="required|length:0,100"
|
||||
></FormKit>
|
||||
help="通常用于生成文章的固定链接"
|
||||
>
|
||||
<template #suffix>
|
||||
<div
|
||||
v-tooltip="'根据标题重新生成别名'"
|
||||
class="group flex h-full cursor-pointer items-center border-l px-3 transition-all hover:bg-gray-100"
|
||||
@click="handleGenerateSlug"
|
||||
>
|
||||
<IconRefreshLine
|
||||
class="h-4 w-4 text-gray-500 group-hover:text-gray-700"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</FormKit>
|
||||
<FormKit
|
||||
v-model="formState.spec.categories"
|
||||
label="分类目录"
|
||||
|
|
|
@ -7,6 +7,7 @@ import { apiClient } from "@/utils/api-client";
|
|||
import {
|
||||
IconArrowLeft,
|
||||
IconArrowRight,
|
||||
IconRefreshLine,
|
||||
Toast,
|
||||
VButton,
|
||||
VModal,
|
||||
|
@ -22,6 +23,7 @@ import cloneDeep from "lodash.clonedeep";
|
|||
import { reset } from "@formkit/core";
|
||||
import { setFocus } from "@/formkit/utils/focus";
|
||||
import AnnotationsForm from "@/components/form/AnnotationsForm.vue";
|
||||
import useSlugify from "@/composables/use-slugify";
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
|
@ -139,6 +141,20 @@ watch(
|
|||
}
|
||||
}
|
||||
);
|
||||
|
||||
// slug
|
||||
const { handleGenerateSlug } = useSlugify(
|
||||
computed(() => formState.value.spec.displayName),
|
||||
computed({
|
||||
get() {
|
||||
return formState.value.spec.slug;
|
||||
},
|
||||
set(value) {
|
||||
formState.value.spec.slug = value;
|
||||
},
|
||||
}),
|
||||
computed(() => !isUpdateMode.value)
|
||||
);
|
||||
</script>
|
||||
<template>
|
||||
<VModal
|
||||
|
@ -181,12 +197,24 @@ watch(
|
|||
></FormKit>
|
||||
<FormKit
|
||||
v-model="formState.spec.slug"
|
||||
help="通常作为标签访问地址标识"
|
||||
help="通常用于生成标签的固定链接"
|
||||
label="别名"
|
||||
name="slug"
|
||||
type="text"
|
||||
validation="required|length:0,50"
|
||||
></FormKit>
|
||||
>
|
||||
<template #suffix>
|
||||
<div
|
||||
v-tooltip="'根据名称重新生成别名'"
|
||||
class="group flex h-full cursor-pointer items-center border-l px-3 transition-all hover:bg-gray-100"
|
||||
@click="handleGenerateSlug"
|
||||
>
|
||||
<IconRefreshLine
|
||||
class="h-4 w-4 text-gray-500 group-hover:text-gray-700"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</FormKit>
|
||||
<FormKit
|
||||
v-model="formState.spec.color"
|
||||
name="color"
|
||||
|
|
Loading…
Reference in New Issue