@opentinyvue/vue-docs 3.24.4 → 3.24.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +5 -4
- package/src/App.vue +1 -1
- package/src/{views/components-doc/components → components}/demo.vue +1 -1
- package/src/{views/components-doc → components}/tiny-robot-chat.vue +15 -11
- package/src/{views/components-doc/composition → composable}/DifyModelProvider.ts +0 -1
- package/src/composable/storage.ts +71 -0
- package/src/{views/components-doc/composition → composable}/utils.ts +5 -1
- package/src/router.js +6 -0
- package/src/views/components-doc/common.vue +8 -8
- package/src/views/comprehensive/Demo.vue +211 -0
- package/src/views/comprehensive/index.vue +391 -0
- package/src/views/comprehensive/products.json +99 -0
- package/src/views/comprehensive/types/index.ts +37 -0
- package/src/views/layout/layout.vue +2 -2
- /package/src/{views/components-doc/components → components}/anchor.vue +0 -0
- /package/src/{views/components-doc/components → components}/api-docs.vue +0 -0
- /package/src/{views/components-doc/components → components}/async-highlight.vue +0 -0
- /package/src/{views/components-doc/components → components}/contributor.vue +0 -0
- /package/src/{views/components-doc/components → components}/float-settings.vue +0 -0
- /package/src/{views/components-doc/components → components}/header.vue +0 -0
- /package/src/{views/components-doc/components → components}/mcp-docs.vue +0 -0
- /package/src/{views/components-doc/components → components}/version-tip.vue +0 -0
- /package/src/{views/components-doc/composition → composable}/useTasksFinish.ts +0 -0
- /package/src/{views/components-doc/composition → composable}/useTinyRobot.ts +0 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opentinyvue/vue-docs",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "3.24.
|
|
4
|
+
"version": "3.24.5",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"devDependencies": {
|
|
7
7
|
"@playwright/test": "~1.49.0",
|
|
@@ -56,6 +56,7 @@
|
|
|
56
56
|
"@vue/shared": "^3.4.31",
|
|
57
57
|
"@vueuse/core": "^12.7.0",
|
|
58
58
|
"@vueuse/head": "0.7.13",
|
|
59
|
+
"crypto-js": "^4.2.0",
|
|
59
60
|
"github-markdown-css": "~5.1.0",
|
|
60
61
|
"highlight.js": "^11.5.1",
|
|
61
62
|
"marked": "^4.3.0",
|
|
@@ -66,17 +67,17 @@
|
|
|
66
67
|
"vue-i18n": "~9.14.3",
|
|
67
68
|
"vue-router": "4.1.5",
|
|
68
69
|
"@opentiny-internal/unplugin-virtual-template": "~0.1.1-beta.0",
|
|
69
|
-
"@opentiny/utils": "~3.24.0",
|
|
70
70
|
"@opentiny/vue": "~3.24.0",
|
|
71
|
+
"@opentiny/utils": "~3.24.0",
|
|
71
72
|
"@opentiny/vue-common": "~3.24.0",
|
|
72
73
|
"@opentiny/vue-design-aurora": "~3.24.0",
|
|
73
74
|
"@opentiny/vue-design-saas": "~3.24.0",
|
|
74
|
-
"@opentiny/vue-design-smb": "~3.24.0",
|
|
75
75
|
"@opentiny/vue-directive": "~3.24.0",
|
|
76
|
+
"@opentiny/vue-design-smb": "~3.24.0",
|
|
76
77
|
"@opentiny/vue-flowchart": "~3.24.0",
|
|
77
78
|
"@opentiny/vue-hooks": "~3.24.0",
|
|
78
|
-
"@opentiny/vue-huicharts": "~3.24.0",
|
|
79
79
|
"@opentiny/vue-icon": "~3.24.0",
|
|
80
|
+
"@opentiny/vue-huicharts": "~3.24.0",
|
|
80
81
|
"@opentiny/vue-icon-multicolor": "~3.24.0",
|
|
81
82
|
"@opentiny/vue-icon-saas": "~3.24.0",
|
|
82
83
|
"@opentiny/vue-locale": "~3.24.0",
|
package/src/App.vue
CHANGED
|
@@ -17,7 +17,7 @@ import { iconClose } from '@opentiny/vue-icon'
|
|
|
17
17
|
import { appData } from './tools'
|
|
18
18
|
import useTheme from './tools/useTheme'
|
|
19
19
|
import { useNextClient } from '@opentiny/next-vue'
|
|
20
|
-
import { globalConversation } from './
|
|
20
|
+
import { globalConversation } from './composable/utils'
|
|
21
21
|
|
|
22
22
|
export default defineComponent({
|
|
23
23
|
name: 'AppVue',
|
|
@@ -75,7 +75,7 @@ import { i18nByKey, getWord } from '@/i18n'
|
|
|
75
75
|
import { $split, fetchDemosFile } from '@/tools'
|
|
76
76
|
import { Tabs as TinyTabs, TabItem as TinyTabItem, Button as TinyButton } from '@opentiny/vue'
|
|
77
77
|
import { AutoTip as vAutoTip } from '@opentiny/vue-directive'
|
|
78
|
-
import { languageMap, vueComponents, getWebdocPath, staticDemoPath } from '../cmp-config'
|
|
78
|
+
import { languageMap, vueComponents, getWebdocPath, staticDemoPath } from '../views/components-doc/cmp-config'
|
|
79
79
|
import { router } from '@/router.js'
|
|
80
80
|
import demoConfig from '@demos/config.js'
|
|
81
81
|
import { useApiMode, useTemplateMode } from '@/tools'
|
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
<!-- mcp-robot弹窗 -->
|
|
3
3
|
<tr-container v-model:show="appData.showTinyRobot" v-model:fullscreen="fullscreen">
|
|
4
4
|
<div v-if="messages.length === 0">
|
|
5
|
-
<tr-welcome title="智能助手" description="您好,我是
|
|
5
|
+
<tr-welcome title="智能助手" description="您好,我是OpenTiny AI智能助手" :icon="welcomeIcon">
|
|
6
6
|
<template #footer>
|
|
7
7
|
<div class="welcome-footer"></div>
|
|
8
8
|
</template>
|
|
9
9
|
</tr-welcome>
|
|
10
10
|
<tr-prompts
|
|
11
|
-
:items="
|
|
11
|
+
:items="customPromptItems"
|
|
12
12
|
:wrap="true"
|
|
13
13
|
item-class="prompt-item"
|
|
14
14
|
class="tiny-prompts"
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
<tr-bubble-list v-else :items="messages" :roles="roles" auto-scroll></tr-bubble-list>
|
|
19
19
|
<template #footer>
|
|
20
20
|
<div class="chat-input">
|
|
21
|
-
<TrSuggestionPills :items="
|
|
21
|
+
<TrSuggestionPills :items="customSuggestionPillItems" @item-click="handleSuggestionPillItemClick" /><br />
|
|
22
22
|
<tr-sender
|
|
23
23
|
ref="senderRef"
|
|
24
24
|
mode="single"
|
|
@@ -42,20 +42,21 @@
|
|
|
42
42
|
<script setup lang="ts">
|
|
43
43
|
import { TrBubbleList, TrContainer, TrPrompts, TrSender, TrWelcome, TrSuggestionPills } from '@opentiny/tiny-robot'
|
|
44
44
|
import { GeneratingStatus } from '@opentiny/tiny-robot-kit'
|
|
45
|
-
import { useTinyRobot } from '
|
|
46
|
-
import { appData } from '
|
|
45
|
+
import { useTinyRobot } from '../composable/useTinyRobot'
|
|
46
|
+
import { appData } from '../tools/appData'
|
|
47
|
+
|
|
48
|
+
const props = defineProps<{
|
|
49
|
+
promptItems: any[]
|
|
50
|
+
suggestionPillItems: any[]
|
|
51
|
+
}>()
|
|
47
52
|
|
|
48
53
|
const {
|
|
49
|
-
client,
|
|
50
54
|
fullscreen,
|
|
51
|
-
show,
|
|
52
55
|
welcomeIcon,
|
|
53
|
-
promptItems,
|
|
54
|
-
computedMessages,
|
|
56
|
+
promptItems: defaultPromptItems,
|
|
55
57
|
messages,
|
|
56
58
|
messageState,
|
|
57
59
|
inputMessage,
|
|
58
|
-
sendMessage,
|
|
59
60
|
abortRequest,
|
|
60
61
|
roles,
|
|
61
62
|
handlePromptItemClick,
|
|
@@ -64,9 +65,12 @@ const {
|
|
|
64
65
|
clearTemplate,
|
|
65
66
|
handleSendMessage,
|
|
66
67
|
handleMessageKeydown,
|
|
67
|
-
suggestionPillItems,
|
|
68
|
+
suggestionPillItems: defaultSuggestionPillItems,
|
|
68
69
|
handleSuggestionPillItemClick
|
|
69
70
|
} = useTinyRobot()
|
|
71
|
+
|
|
72
|
+
const customPromptItems = props.promptItems || defaultPromptItems
|
|
73
|
+
const customSuggestionPillItems = props.suggestionPillItems || defaultSuggestionPillItems
|
|
70
74
|
</script>
|
|
71
75
|
|
|
72
76
|
<style scoped lang="less">
|
|
@@ -33,7 +33,6 @@ export class DifyModelProvider extends BaseModelProvider {
|
|
|
33
33
|
try {
|
|
34
34
|
// 验证请求的messages属性,必须是数组,且每个消息必须有role\content属性
|
|
35
35
|
const lastMessage = request.messages[request.messages.length - 1].content
|
|
36
|
-
|
|
37
36
|
// 模拟异步流式响应
|
|
38
37
|
const response = await fetch(`${this.config.apiUrl}/chat-messages`, {
|
|
39
38
|
method: 'POST',
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { ref, watch } from 'vue'
|
|
2
|
+
|
|
3
|
+
function parse(str) {
|
|
4
|
+
if (str === null) return undefined
|
|
5
|
+
const type = str[0]
|
|
6
|
+
const strVal = str.slice(1)
|
|
7
|
+
// 对象时,有可能是Date, 否则反解析后统一是对象
|
|
8
|
+
if (type === 'o' || type === 'b') {
|
|
9
|
+
let val = JSON.parse(strVal)
|
|
10
|
+
return typeof val === 'string' ? new Date(val) : val
|
|
11
|
+
}
|
|
12
|
+
if (type === 'n') return +Number(strVal)
|
|
13
|
+
if (type === 's') return strVal
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// 带前缀的保存值
|
|
17
|
+
function save(store, k, v) {
|
|
18
|
+
const type = typeof v
|
|
19
|
+
store.setItem(k, type[0] + (type === 'object' ? JSON.stringify(v) : v))
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* 快速的保存值到 sessionStorage, localStorage.
|
|
24
|
+
* 支持基本类型,时间,数组,对象,null,不存在的键值返回undefined 。
|
|
25
|
+
* 不支持:Map,Set, 以及多级串联赋值,比如:$session.obj.name="abcd"
|
|
26
|
+
*/
|
|
27
|
+
function handler(storage) {
|
|
28
|
+
return {
|
|
29
|
+
get(target, propKey, receiver) {
|
|
30
|
+
return parse(storage.getItem(propKey))
|
|
31
|
+
},
|
|
32
|
+
set(target, propKey, value) {
|
|
33
|
+
save(storage, propKey, value)
|
|
34
|
+
return true
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** * 快速读写sessionStorage 示例: $session.abc="shen" */
|
|
40
|
+
const $session = new Proxy({}, handler(sessionStorage))
|
|
41
|
+
|
|
42
|
+
/** * 快速读写 localStorage 示例: $local.abc="shen" */
|
|
43
|
+
const $local = new Proxy({}, handler(localStorage))
|
|
44
|
+
|
|
45
|
+
/** * 全局共享值,刷新即丢失! 示例: $cache.abc="shen" */
|
|
46
|
+
const $cache = {}
|
|
47
|
+
|
|
48
|
+
const typeMatcher = { session: $session, local: $local, api: null }
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* 用于记录用户行为,并保存到session,local 或api接口(api保存的功能还未实现)
|
|
52
|
+
* 示例:useAutoStore("session","key1")
|
|
53
|
+
* useAutoStore("session","key2",100)
|
|
54
|
+
* useAutoStore("session","key2",$session.key2 || 100)
|
|
55
|
+
* @param type 自动存储到的目标
|
|
56
|
+
* @param key 存储时的key
|
|
57
|
+
* @param defaultValue 默认值。
|
|
58
|
+
* @returns 响应式ref
|
|
59
|
+
*/
|
|
60
|
+
const useAutoStore = (type, key, defaultValue) => {
|
|
61
|
+
let refVar = ref(typeMatcher[type][key])
|
|
62
|
+
watch(refVar, (curr, prev) => {
|
|
63
|
+
typeMatcher[type][key] = curr
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
refVar.value = refVar.value ?? defaultValue
|
|
67
|
+
|
|
68
|
+
return refVar
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export { $session, $local, $cache, useAutoStore }
|
|
@@ -5,7 +5,11 @@
|
|
|
5
5
|
|
|
6
6
|
import type { ChatMessage, ChatCompletionResponse, StreamHandler } from '@opentiny/tiny-robot-kit'
|
|
7
7
|
import type { ChatCompletionRequest } from '@opentiny/tiny-robot-kit'
|
|
8
|
-
import type
|
|
8
|
+
import { ref, type Ref } from 'vue'
|
|
9
|
+
|
|
10
|
+
export { $local } from './storage'
|
|
11
|
+
|
|
12
|
+
export const showTinyRobot = ref(true)
|
|
9
13
|
|
|
10
14
|
export const globalConversation = {
|
|
11
15
|
id: '',
|
package/src/router.js
CHANGED
|
@@ -7,6 +7,7 @@ const Components = () => import('@/views/components-doc/index.vue')
|
|
|
7
7
|
const Docs = () => import('@/views/docs/docs.vue')
|
|
8
8
|
const Overview = () => import('@/views/overview.vue')
|
|
9
9
|
const Features = () => import('@/views/features.vue')
|
|
10
|
+
const Comprehensive = () => import('@/views/comprehensive/index.vue')
|
|
10
11
|
|
|
11
12
|
const context = import.meta.env.VITE_CONTEXT
|
|
12
13
|
|
|
@@ -18,6 +19,11 @@ let routes = [
|
|
|
18
19
|
name: 'overview',
|
|
19
20
|
children: [{ name: 'Overview', path: '', component: Overview, meta: { title: '组件总览 | TinyVue' } }]
|
|
20
21
|
},
|
|
22
|
+
{
|
|
23
|
+
path: `${context}:all?/zh-CN/:theme/comprehensive`,
|
|
24
|
+
component: Comprehensive,
|
|
25
|
+
name: 'comprehensive'
|
|
26
|
+
},
|
|
21
27
|
// 文档
|
|
22
28
|
{
|
|
23
29
|
path: `${context}:all?/:lang/:theme/docs/:docId`,
|
|
@@ -99,16 +99,16 @@ import { debounce } from '@opentiny/utils'
|
|
|
99
99
|
import { i18nByKey, getWord, $clone, useApiMode } from '@/tools'
|
|
100
100
|
import { router } from '@/router.js'
|
|
101
101
|
import { getWebdocPath } from './cmp-config'
|
|
102
|
-
import DemoBox from '
|
|
103
|
-
import AsideAnchor from '
|
|
104
|
-
import ComponentHeader from '
|
|
105
|
-
import ComponentContributor from '
|
|
106
|
-
import ApiDocs from '
|
|
107
|
-
import McpDocs from '
|
|
108
|
-
import useTasksFinish from '
|
|
102
|
+
import DemoBox from '../../components/demo.vue'
|
|
103
|
+
import AsideAnchor from '../../components/anchor.vue'
|
|
104
|
+
import ComponentHeader from '../../components/header.vue'
|
|
105
|
+
import ComponentContributor from '../../components/contributor.vue'
|
|
106
|
+
import ApiDocs from '../../components/api-docs.vue'
|
|
107
|
+
import McpDocs from '../../components/mcp-docs.vue'
|
|
108
|
+
import useTasksFinish from '../../composable/useTasksFinish'
|
|
109
109
|
import { appData } from '../../tools/appData'
|
|
110
110
|
|
|
111
|
-
import robotChat from '
|
|
111
|
+
import robotChat from '../../components/tiny-robot-chat.vue'
|
|
112
112
|
|
|
113
113
|
const props = defineProps({ loadData: {}, appMode: {}, demoKey: {} })
|
|
114
114
|
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="products-page">
|
|
3
|
+
<div class="page-header">
|
|
4
|
+
<h3>商品管理</h3>
|
|
5
|
+
</div>
|
|
6
|
+
<div class="page-content">
|
|
7
|
+
<div class="button-box">
|
|
8
|
+
<tiny-button type="info" @click="addProductToEdit"> 添加商品 </tiny-button>
|
|
9
|
+
<tiny-button type="danger" @click="removeProduct"> 删除商品 </tiny-button>
|
|
10
|
+
<tiny-button type="success" @click="saveProduct"> 保存 </tiny-button>
|
|
11
|
+
</div>
|
|
12
|
+
<tiny-grid
|
|
13
|
+
auto-resize
|
|
14
|
+
ref="gridRef"
|
|
15
|
+
:data="products"
|
|
16
|
+
:height="500"
|
|
17
|
+
:edit-config="{ trigger: 'click', mode: 'cell', showStatus: true }"
|
|
18
|
+
:tiny_mcp_config="{
|
|
19
|
+
server,
|
|
20
|
+
business: {
|
|
21
|
+
id: 'product-list',
|
|
22
|
+
description: '商品列表'
|
|
23
|
+
}
|
|
24
|
+
}"
|
|
25
|
+
>
|
|
26
|
+
<tiny-grid-column type="index" width="50" />
|
|
27
|
+
<tiny-grid-column type="selection" width="50" />
|
|
28
|
+
<tiny-grid-column title="商品图片" width="100">
|
|
29
|
+
<template #default="{ row }">
|
|
30
|
+
<tiny-image :src="row.image" :preview-src-list="[row.image]" class="product-image" />
|
|
31
|
+
</template>
|
|
32
|
+
</tiny-grid-column>
|
|
33
|
+
|
|
34
|
+
<tiny-grid-column field="name" title="商品名称" :editor="{ component: 'input' }" />
|
|
35
|
+
<tiny-grid-column
|
|
36
|
+
field="price"
|
|
37
|
+
:editor="{
|
|
38
|
+
component: 'input',
|
|
39
|
+
attrs: { type: 'number' }
|
|
40
|
+
}"
|
|
41
|
+
title="价格"
|
|
42
|
+
>
|
|
43
|
+
<template #default="{ row }"> ¥{{ row.price }} </template>
|
|
44
|
+
</tiny-grid-column>
|
|
45
|
+
<tiny-grid-column
|
|
46
|
+
field="stock"
|
|
47
|
+
:editor="{
|
|
48
|
+
component: 'input',
|
|
49
|
+
attrs: { type: 'number' }
|
|
50
|
+
}"
|
|
51
|
+
title="库存"
|
|
52
|
+
/>
|
|
53
|
+
<tiny-grid-column
|
|
54
|
+
field="category"
|
|
55
|
+
:editor="{
|
|
56
|
+
component: 'select',
|
|
57
|
+
options: [
|
|
58
|
+
{ label: '手机', value: 'phones' },
|
|
59
|
+
{ label: '笔记本', value: 'laptops' },
|
|
60
|
+
{ label: '平板', value: 'tablets' }
|
|
61
|
+
]
|
|
62
|
+
}"
|
|
63
|
+
title="分类"
|
|
64
|
+
>
|
|
65
|
+
<template #default="{ row }">
|
|
66
|
+
{{ categoryLabels[row.category] }}
|
|
67
|
+
</template>
|
|
68
|
+
</tiny-grid-column>
|
|
69
|
+
<tiny-grid-column
|
|
70
|
+
field="status"
|
|
71
|
+
:editor="{
|
|
72
|
+
component: 'select',
|
|
73
|
+
options: [
|
|
74
|
+
{ label: '上架', value: 'on' },
|
|
75
|
+
{ label: '下架', value: 'off' }
|
|
76
|
+
]
|
|
77
|
+
}"
|
|
78
|
+
title="状态"
|
|
79
|
+
>
|
|
80
|
+
<template #default="{ row }">
|
|
81
|
+
<tiny-tag :type="row.status === 'on' ? 'success' : 'warning'">
|
|
82
|
+
{{ row.status === 'on' ? '上架' : '下架' }}
|
|
83
|
+
</tiny-tag>
|
|
84
|
+
</template>
|
|
85
|
+
</tiny-grid-column>
|
|
86
|
+
</tiny-grid>
|
|
87
|
+
</div>
|
|
88
|
+
</div>
|
|
89
|
+
</template>
|
|
90
|
+
|
|
91
|
+
<script setup lang="ts">
|
|
92
|
+
import { ref } from 'vue'
|
|
93
|
+
import productsData from './products.json'
|
|
94
|
+
import { $local } from '../../composable/utils'
|
|
95
|
+
import { useNextServer } from '@opentiny/next-vue'
|
|
96
|
+
import { TinyGrid, TinyGridColumn, TinyButton, TinyTag, TinyModal, TinyImage } from '@opentiny/vue'
|
|
97
|
+
|
|
98
|
+
if (!$local.products) {
|
|
99
|
+
$local.products = productsData
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const products = ref($local.products)
|
|
103
|
+
const gridRef = ref(null)
|
|
104
|
+
|
|
105
|
+
const categoryLabels: Record<string, string> = {
|
|
106
|
+
phones: '手机',
|
|
107
|
+
laptops: '笔记本',
|
|
108
|
+
tablets: '平板'
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// 新增商品到编辑弹窗
|
|
112
|
+
const addProductToEdit = async () => {
|
|
113
|
+
gridRef?.value?.insert({
|
|
114
|
+
'image': 'https://img1.baidu.com/it/u=1559062020,1043707656&fm=253&fmt=auto&app=120&f=JPEG?w=500&h=500',
|
|
115
|
+
price: 10000,
|
|
116
|
+
stock: 100,
|
|
117
|
+
category: 'phones',
|
|
118
|
+
status: 'on'
|
|
119
|
+
})
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const removeProduct = () => {
|
|
123
|
+
const selectedRows = gridRef?.value?.getSelectRecords()
|
|
124
|
+
if (selectedRows.length === 0) {
|
|
125
|
+
TinyModal.confirm({
|
|
126
|
+
message: '请选择要删除的商品',
|
|
127
|
+
title: '删除商品',
|
|
128
|
+
status: 'warning'
|
|
129
|
+
})
|
|
130
|
+
return
|
|
131
|
+
}
|
|
132
|
+
if (selectedRows.length > 0) {
|
|
133
|
+
gridRef?.value?.removeSelecteds()
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const saveProduct = () => {
|
|
138
|
+
setTimeout(() => {
|
|
139
|
+
const data = gridRef?.value?.getTableData()
|
|
140
|
+
$local.products = data.tableData
|
|
141
|
+
TinyModal.message({
|
|
142
|
+
message: '保存成功',
|
|
143
|
+
status: 'success'
|
|
144
|
+
})
|
|
145
|
+
}, 1000)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const { server } = useNextServer({
|
|
149
|
+
serverInfo: { name: 'commodity-config', version: '1.0.0' }
|
|
150
|
+
})
|
|
151
|
+
</script>
|
|
152
|
+
|
|
153
|
+
<style scoped lang="less">
|
|
154
|
+
.products-page {
|
|
155
|
+
.page-header {
|
|
156
|
+
display: flex;
|
|
157
|
+
justify-content: space-between;
|
|
158
|
+
align-items: center;
|
|
159
|
+
margin-bottom: 20px;
|
|
160
|
+
padding: 16px 20px;
|
|
161
|
+
background-color: #ffffff;
|
|
162
|
+
border-radius: 8px;
|
|
163
|
+
position: relative;
|
|
164
|
+
border: 1px solid #edf2f7;
|
|
165
|
+
|
|
166
|
+
&::before {
|
|
167
|
+
content: '';
|
|
168
|
+
position: absolute;
|
|
169
|
+
top: 50%;
|
|
170
|
+
left: 0;
|
|
171
|
+
width: 4px;
|
|
172
|
+
height: 24px;
|
|
173
|
+
background: #1677ff;
|
|
174
|
+
border-radius: 2px;
|
|
175
|
+
transform: translateY(-50%);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
h3 {
|
|
179
|
+
margin: 0;
|
|
180
|
+
font-size: 20px;
|
|
181
|
+
font-weight: 600;
|
|
182
|
+
color: #1f2937;
|
|
183
|
+
position: relative;
|
|
184
|
+
padding-left: 20px;
|
|
185
|
+
letter-spacing: 0.3px;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
.button-box {
|
|
191
|
+
display: flex;
|
|
192
|
+
gap: 16px;
|
|
193
|
+
margin-bottom: 20px;
|
|
194
|
+
justify-content: flex-end;
|
|
195
|
+
}
|
|
196
|
+
.loading-state {
|
|
197
|
+
padding: 20px;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
.product-image {
|
|
201
|
+
width: 40px;
|
|
202
|
+
height: 40px;
|
|
203
|
+
border-radius: 4px;
|
|
204
|
+
}
|
|
205
|
+
.page-content {
|
|
206
|
+
padding: 20px;
|
|
207
|
+
background: #fff;
|
|
208
|
+
border-radius: 8px;
|
|
209
|
+
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.03);
|
|
210
|
+
}
|
|
211
|
+
</style>
|
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="header">
|
|
3
|
+
<div class="qr-code-trigger" @click="showQrCode = true">
|
|
4
|
+
<IconAi class="qr-icon" />
|
|
5
|
+
<div class="qr-text-wrapper">
|
|
6
|
+
<span class="qr-label">手机遥控</span>
|
|
7
|
+
<span class="qr-desc">扫码体验移动端操作</span>
|
|
8
|
+
</div>
|
|
9
|
+
</div>
|
|
10
|
+
</div>
|
|
11
|
+
<div class="app-container">
|
|
12
|
+
<!-- 主体内容区域 -->
|
|
13
|
+
<div class="main-content">
|
|
14
|
+
<Demo />
|
|
15
|
+
</div>
|
|
16
|
+
<div class="right-panel" :class="{ collapsed: !appData.showTinyRobot }">
|
|
17
|
+
<tiny-robot-chat :prompt-items="promptItems" :suggestion-pill-items="suggestionPillItems" />
|
|
18
|
+
</div>
|
|
19
|
+
<IconAi @click="appData.showTinyRobot = !appData.showTinyRobot" class="style-settings-icon"></IconAi>
|
|
20
|
+
</div>
|
|
21
|
+
|
|
22
|
+
<!-- QR Code Modal -->
|
|
23
|
+
<Teleport to="body">
|
|
24
|
+
<div class="qr-modal" v-if="showQrCode" @click.self="showQrCode = false">
|
|
25
|
+
<div class="qr-modal-content">
|
|
26
|
+
<div class="qr-modal-header">
|
|
27
|
+
<h3>手机遥控体验</h3>
|
|
28
|
+
</div>
|
|
29
|
+
<div class="qr-modal-body">
|
|
30
|
+
<div class="qr-wrapper">
|
|
31
|
+
<tiny-qr-code :value="sessionUrl" :size="200" color="#1677ff"></tiny-qr-code>
|
|
32
|
+
<div class="qr-status">
|
|
33
|
+
<div class="status-dot"></div>
|
|
34
|
+
<span>链接已生成</span>
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
<div class="qr-instructions">
|
|
38
|
+
<p class="instruction-title">使用说明</p>
|
|
39
|
+
<ol class="instruction-steps">
|
|
40
|
+
<li>使用微信扫一扫,扫描上方二维码</li>
|
|
41
|
+
<li>然后点击微信右上角的 "..." 图标</li>
|
|
42
|
+
<li>选择在默认浏览器中打开</li>
|
|
43
|
+
<li>可以选择直接语音交互也可使用AI对话框进行交互</li>
|
|
44
|
+
</ol>
|
|
45
|
+
</div>
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
</Teleport>
|
|
50
|
+
</template>
|
|
51
|
+
|
|
52
|
+
<script setup lang="ts">
|
|
53
|
+
import { watch, ref, h } from 'vue'
|
|
54
|
+
import TinyRobotChat from '@/components/tiny-robot-chat.vue'
|
|
55
|
+
import { globalConversation } from '@/composable/utils'
|
|
56
|
+
import { IconAi } from '@opentiny/tiny-robot-svgs'
|
|
57
|
+
import CryptoJS from 'crypto-js'
|
|
58
|
+
import { TinyQrCode } from '@opentiny/vue'
|
|
59
|
+
import Demo from './demo.vue'
|
|
60
|
+
import { appData } from '@/tools/appData'
|
|
61
|
+
|
|
62
|
+
appData.showTinyRobot = true
|
|
63
|
+
const showQrCode = ref(false)
|
|
64
|
+
const sessionUrl = ref('placeholder')
|
|
65
|
+
|
|
66
|
+
const promptItems = [
|
|
67
|
+
{
|
|
68
|
+
label: '识别网页的内容',
|
|
69
|
+
description: '帮我在商品列表中查询最贵的手机和最便宜的笔记本',
|
|
70
|
+
icon: h('span', { style: { fontSize: '18px' } }, '💡')
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
label: '智能操作网页',
|
|
74
|
+
description: '帮我在商品列表中删除最贵的且分类为手机的商品',
|
|
75
|
+
icon: h('span', { style: { fontSize: '18px' } }, '🕹')
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
label: '智能操作网页',
|
|
79
|
+
description: '帮我在商品列表中添加一个华为p60品牌的手机商品',
|
|
80
|
+
icon: h('span', { style: { fontSize: '18px' } }, '🕹')
|
|
81
|
+
}
|
|
82
|
+
]
|
|
83
|
+
|
|
84
|
+
const suggestionPillItems = [
|
|
85
|
+
{
|
|
86
|
+
id: '1',
|
|
87
|
+
text: '商品列表',
|
|
88
|
+
icon: h('span', { style: { fontSize: '18px' } }, '🏢')
|
|
89
|
+
}
|
|
90
|
+
]
|
|
91
|
+
|
|
92
|
+
watch(
|
|
93
|
+
() => globalConversation.sessionId,
|
|
94
|
+
(newVal) => {
|
|
95
|
+
if (newVal) {
|
|
96
|
+
const encryptedId = CryptoJS.AES.encrypt(newVal, 'secret-session-id').toString()
|
|
97
|
+
|
|
98
|
+
const secretId = encodeURIComponent(encryptedId)
|
|
99
|
+
sessionUrl.value = 'http://39.108.160.245?id=' + secretId
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
{ immediate: true }
|
|
103
|
+
)
|
|
104
|
+
</script>
|
|
105
|
+
|
|
106
|
+
<style scoped>
|
|
107
|
+
.header {
|
|
108
|
+
width: calc(100% - 502px);
|
|
109
|
+
display: flex;
|
|
110
|
+
justify-content: space-between;
|
|
111
|
+
align-items: center;
|
|
112
|
+
padding: 10px 20px;
|
|
113
|
+
background-color: #f5f5f5;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.qr-code-trigger {
|
|
117
|
+
display: flex;
|
|
118
|
+
align-items: center;
|
|
119
|
+
cursor: pointer;
|
|
120
|
+
padding: 12px 20px;
|
|
121
|
+
border-radius: 12px;
|
|
122
|
+
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
123
|
+
background: linear-gradient(135deg, #1677ff 0%, #06b6d4 100%);
|
|
124
|
+
position: relative;
|
|
125
|
+
overflow: hidden;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.qr-code-trigger::before {
|
|
129
|
+
content: '';
|
|
130
|
+
position: absolute;
|
|
131
|
+
top: 0;
|
|
132
|
+
left: 0;
|
|
133
|
+
width: 100%;
|
|
134
|
+
height: 100%;
|
|
135
|
+
background: linear-gradient(135deg, rgba(255, 255, 255, 0.1), rgba(255, 255, 255, 0));
|
|
136
|
+
transform: translateX(-100%);
|
|
137
|
+
transition: transform 0.6s cubic-bezier(0.4, 0, 0.2, 1);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.qr-code-trigger:hover {
|
|
141
|
+
transform: translateY(-2px);
|
|
142
|
+
box-shadow: 0 8px 20px rgba(22, 119, 255, 0.3);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.qr-code-trigger:hover::before {
|
|
146
|
+
transform: translateX(100%);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
.qr-icon {
|
|
150
|
+
font-size: 24px;
|
|
151
|
+
margin-right: 12px;
|
|
152
|
+
color: white;
|
|
153
|
+
filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.1));
|
|
154
|
+
animation: float 3s ease-in-out infinite;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
.qr-text-wrapper {
|
|
158
|
+
display: flex;
|
|
159
|
+
flex-direction: column;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
.qr-label {
|
|
163
|
+
font-size: 16px;
|
|
164
|
+
font-weight: 600;
|
|
165
|
+
color: white;
|
|
166
|
+
margin-bottom: 2px;
|
|
167
|
+
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.qr-desc {
|
|
171
|
+
font-size: 13px;
|
|
172
|
+
color: rgba(255, 255, 255, 0.9);
|
|
173
|
+
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
@keyframes float {
|
|
177
|
+
0%,
|
|
178
|
+
100% {
|
|
179
|
+
transform: translateY(0);
|
|
180
|
+
}
|
|
181
|
+
50% {
|
|
182
|
+
transform: translateY(-3px);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
.qr-modal {
|
|
187
|
+
position: fixed;
|
|
188
|
+
top: 0;
|
|
189
|
+
left: 0;
|
|
190
|
+
width: 100%;
|
|
191
|
+
height: 100%;
|
|
192
|
+
background-color: rgba(0, 0, 0, 0.5);
|
|
193
|
+
display: flex;
|
|
194
|
+
justify-content: center;
|
|
195
|
+
align-items: center;
|
|
196
|
+
z-index: 1000;
|
|
197
|
+
animation: fadeIn 0.3s ease;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
.qr-modal-content {
|
|
201
|
+
background: white;
|
|
202
|
+
border-radius: 12px;
|
|
203
|
+
padding: 32px;
|
|
204
|
+
width: 460px;
|
|
205
|
+
box-shadow:
|
|
206
|
+
0 20px 25px -5px rgba(0, 0, 0, 0.1),
|
|
207
|
+
0 10px 10px -5px rgba(0, 0, 0, 0.04);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
.qr-modal-header {
|
|
211
|
+
margin-bottom: 32px;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
.qr-modal-header h3 {
|
|
215
|
+
font-size: 22px;
|
|
216
|
+
font-weight: 600;
|
|
217
|
+
color: #1f2937;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
.close-icon {
|
|
221
|
+
cursor: pointer;
|
|
222
|
+
font-size: 20px;
|
|
223
|
+
color: #999;
|
|
224
|
+
transition: color 0.3s;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
.close-icon:hover {
|
|
228
|
+
color: #666;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
.qr-modal-body {
|
|
232
|
+
display: flex;
|
|
233
|
+
flex-direction: column;
|
|
234
|
+
align-items: center;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
.qr-wrapper {
|
|
238
|
+
background-color: #f8f9fa;
|
|
239
|
+
padding: 24px;
|
|
240
|
+
border-radius: 8px;
|
|
241
|
+
display: flex;
|
|
242
|
+
flex-direction: column;
|
|
243
|
+
align-items: center;
|
|
244
|
+
margin-bottom: 24px;
|
|
245
|
+
position: relative;
|
|
246
|
+
animation: glow 2s ease-in-out infinite;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
.qr-wrapper::before {
|
|
250
|
+
content: '';
|
|
251
|
+
position: absolute;
|
|
252
|
+
top: -2px;
|
|
253
|
+
left: -2px;
|
|
254
|
+
right: -2px;
|
|
255
|
+
bottom: -2px;
|
|
256
|
+
background: linear-gradient(45deg, #1677ff, #10b981, #1677ff);
|
|
257
|
+
border-radius: 10px;
|
|
258
|
+
z-index: -1;
|
|
259
|
+
filter: blur(8px);
|
|
260
|
+
opacity: 0.5;
|
|
261
|
+
transition: opacity 0.3s ease;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
.qr-wrapper:hover::before {
|
|
265
|
+
opacity: 0.7;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
@keyframes glow {
|
|
269
|
+
0% {
|
|
270
|
+
box-shadow:
|
|
271
|
+
0 0 5px rgba(22, 119, 255, 0.2),
|
|
272
|
+
0 0 10px rgba(22, 119, 255, 0.2),
|
|
273
|
+
0 0 15px rgba(22, 119, 255, 0.2);
|
|
274
|
+
}
|
|
275
|
+
50% {
|
|
276
|
+
box-shadow:
|
|
277
|
+
0 0 10px rgba(22, 119, 255, 0.3),
|
|
278
|
+
0 0 20px rgba(22, 119, 255, 0.3),
|
|
279
|
+
0 0 30px rgba(22, 119, 255, 0.3);
|
|
280
|
+
}
|
|
281
|
+
100% {
|
|
282
|
+
box-shadow:
|
|
283
|
+
0 0 5px rgba(22, 119, 255, 0.2),
|
|
284
|
+
0 0 10px rgba(22, 119, 255, 0.2),
|
|
285
|
+
0 0 15px rgba(22, 119, 255, 0.2);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
.qr-status {
|
|
290
|
+
display: flex;
|
|
291
|
+
align-items: center;
|
|
292
|
+
margin-top: 16px;
|
|
293
|
+
background: #fff;
|
|
294
|
+
padding: 6px 12px;
|
|
295
|
+
border-radius: 16px;
|
|
296
|
+
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
.status-dot {
|
|
300
|
+
width: 8px;
|
|
301
|
+
height: 8px;
|
|
302
|
+
background-color: #10b981;
|
|
303
|
+
border-radius: 50%;
|
|
304
|
+
margin-right: 8px;
|
|
305
|
+
animation: pulse 2s infinite;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
.qr-instructions {
|
|
309
|
+
background-color: #f8f9fa;
|
|
310
|
+
border-radius: 8px;
|
|
311
|
+
padding: 20px;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
.instruction-title {
|
|
315
|
+
font-size: 16px;
|
|
316
|
+
font-weight: 500;
|
|
317
|
+
color: #1f2937;
|
|
318
|
+
margin-bottom: 12px;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
.instruction-steps {
|
|
322
|
+
margin: 0;
|
|
323
|
+
padding-left: 20px;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
.instruction-steps li {
|
|
327
|
+
color: #4b5563;
|
|
328
|
+
margin-bottom: 8px;
|
|
329
|
+
font-size: 14px;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
.instruction-steps li:last-child {
|
|
333
|
+
margin-bottom: 0;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
@keyframes pulse {
|
|
337
|
+
0% {
|
|
338
|
+
box-shadow: 0 0 0 0 rgba(16, 185, 129, 0.4);
|
|
339
|
+
}
|
|
340
|
+
70% {
|
|
341
|
+
box-shadow: 0 0 0 6px rgba(16, 185, 129, 0);
|
|
342
|
+
}
|
|
343
|
+
100% {
|
|
344
|
+
box-shadow: 0 0 0 0 rgba(16, 185, 129, 0);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
@keyframes fadeIn {
|
|
349
|
+
from {
|
|
350
|
+
opacity: 0;
|
|
351
|
+
}
|
|
352
|
+
to {
|
|
353
|
+
opacity: 1;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
.app-container {
|
|
358
|
+
display: flex;
|
|
359
|
+
height: 100%;
|
|
360
|
+
position: relative;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
.main-content {
|
|
364
|
+
padding: 10px 10px;
|
|
365
|
+
height: 100%;
|
|
366
|
+
width: calc(100% - 502px);
|
|
367
|
+
position: relative;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
.right-panel {
|
|
371
|
+
width: 480px;
|
|
372
|
+
height: 100%;
|
|
373
|
+
position: relative;
|
|
374
|
+
background: #fff;
|
|
375
|
+
border-left: 1px solid #e4e7ed;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
.right-panel.collapsed {
|
|
379
|
+
width: 0;
|
|
380
|
+
overflow: hidden;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
.style-settings-icon {
|
|
384
|
+
position: fixed;
|
|
385
|
+
bottom: 100px;
|
|
386
|
+
right: 100px;
|
|
387
|
+
font-size: 24px;
|
|
388
|
+
z-index: 30;
|
|
389
|
+
cursor: pointer;
|
|
390
|
+
}
|
|
391
|
+
</style>
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
[
|
|
2
|
+
|
|
3
|
+
{
|
|
4
|
+
"id": 1,
|
|
5
|
+
"name": "iPhone 16",
|
|
6
|
+
"price": 5999,
|
|
7
|
+
"description": "苹果手机",
|
|
8
|
+
"image": "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg.alicdn.com%2Fbao%2Fuploaded%2FO1CN011qHTQ81edxfMcnfNR_%21%216000000003895-0-yinhe.jpg_300x300.jpg&refer=http%3A%2F%2Fimg.alicdn.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1750986769&t=4570d81c9f0301ab1745c75dfff9f272",
|
|
9
|
+
"category": "phones",
|
|
10
|
+
"stock": 100,
|
|
11
|
+
"status": "on",
|
|
12
|
+
"createdAt": "2024-03-20",
|
|
13
|
+
"updatedAt": "2025-05-29T21:56:30.481Z"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"id": 2,
|
|
17
|
+
"name": "MacBook Pro",
|
|
18
|
+
"price": 12999,
|
|
19
|
+
"description": "笔记本电脑",
|
|
20
|
+
"image": "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fg-search1.alicdn.com%2Fimg%2Fbao%2Fuploaded%2Fi3%2F1831488473%2FO1CN01I8MRyC2CSgWGbhVjJ_%21%211831488473.jpg_300x300.jpg&refer=http%3A%2F%2Fg-search1.alicdn.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1750986810&t=9eb9c9e0765244130200dd0f935b9cea",
|
|
21
|
+
"category": "laptops",
|
|
22
|
+
"stock": 50,
|
|
23
|
+
"status": "off",
|
|
24
|
+
"createdAt": "2024-03-20",
|
|
25
|
+
"updatedAt": "2024-03-20"
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"id": 3,
|
|
29
|
+
"name": "iPad",
|
|
30
|
+
"price": 2399,
|
|
31
|
+
"description": "平板电脑",
|
|
32
|
+
"image": "https://respic.3d66.com/coverimg/cache/f2d1/53893cd4d00efe87132f1e3371b72339.jpg%21medium-size-2?v=17611204&k=D41D8CD98F00B204E9800998ECF8427E",
|
|
33
|
+
"category": "tablets",
|
|
34
|
+
"stock": 12999,
|
|
35
|
+
"status": "on",
|
|
36
|
+
"createdAt": "2025-05-27T00:49:29.993Z",
|
|
37
|
+
"updatedAt": "2025-05-29T16:11:49.743Z"
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"id": 4,
|
|
41
|
+
"name": "Huawei Pura 70",
|
|
42
|
+
"price": 6499,
|
|
43
|
+
"description": "华为手机",
|
|
44
|
+
"image": "https://img1.baidu.com/it/u=1559062020,1043707656&fm=253&fmt=auto&app=120&f=JPEG?w=500&h=500",
|
|
45
|
+
"category": "phones",
|
|
46
|
+
"stock": 1999,
|
|
47
|
+
"status": "on",
|
|
48
|
+
"createdAt": "2025-05-27T00:54:38.771Z",
|
|
49
|
+
"updatedAt": "2025-05-29T22:29:40.124Z"
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"id": 5,
|
|
53
|
+
"name": "Huawei Mate XT ultimate",
|
|
54
|
+
"price": 23999,
|
|
55
|
+
"description": "华为手机 非凡大师",
|
|
56
|
+
"image": "https://img2.baidu.com/it/u=2892977680,910281472&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500",
|
|
57
|
+
"category": "phones",
|
|
58
|
+
"stock": 100,
|
|
59
|
+
"status": "on",
|
|
60
|
+
"createdAt": "2025-05-27T00:54:38.771Z",
|
|
61
|
+
"updatedAt": "2025-05-30T17:40:59.199Z"
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"id": 6,
|
|
65
|
+
"name": "小米15",
|
|
66
|
+
"price": 4999,
|
|
67
|
+
"description": "小米手机",
|
|
68
|
+
"image": "https://img2.baidu.com/it/u=2892977680,910281472&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500",
|
|
69
|
+
"category": "phones",
|
|
70
|
+
"stock": 222,
|
|
71
|
+
"status": "off",
|
|
72
|
+
"createdAt": "2025-05-29T00:30:02.226Z",
|
|
73
|
+
"updatedAt": "2025-05-29T16:14:35.150Z"
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"id": 7,
|
|
77
|
+
"name": "小米 13",
|
|
78
|
+
"price": 3999,
|
|
79
|
+
"description": "小米手机",
|
|
80
|
+
"image": "https://img1.baidu.com/it/u=809120544,2106407569&fm=253&fmt=auto&app=120&f=JPEG?w=500&h=500",
|
|
81
|
+
"category": "phones",
|
|
82
|
+
"stock": 2222,
|
|
83
|
+
"status": "on",
|
|
84
|
+
"createdAt": "2025-05-29T00:32:46.308Z",
|
|
85
|
+
"updatedAt": "2025-05-30T21:28:28.543Z"
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
"id": 8,
|
|
89
|
+
"name": "Vivo X90",
|
|
90
|
+
"price": 2999,
|
|
91
|
+
"description": "Vivo手机",
|
|
92
|
+
"image": "https://img2.baidu.com/it/u=2892977680,910281472&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500",
|
|
93
|
+
"category": "phones",
|
|
94
|
+
"stock": 3999,
|
|
95
|
+
"status": "on",
|
|
96
|
+
"createdAt": "2025-05-27T00:54:38.771Z",
|
|
97
|
+
"updatedAt": "2025-05-30T17:40:59.199Z"
|
|
98
|
+
}
|
|
99
|
+
]
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// 用户类型
|
|
2
|
+
export interface User {
|
|
3
|
+
id: number
|
|
4
|
+
username: string
|
|
5
|
+
avatar?: string
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
// 商品类型
|
|
9
|
+
export interface Product {
|
|
10
|
+
id: number
|
|
11
|
+
name: string
|
|
12
|
+
price: number
|
|
13
|
+
description?: string
|
|
14
|
+
image: string
|
|
15
|
+
category: string
|
|
16
|
+
stock: number
|
|
17
|
+
status: 'on' | 'off' // on: 上架, off: 下架
|
|
18
|
+
createdAt?: string
|
|
19
|
+
updatedAt?: string
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// 登录表单类型
|
|
23
|
+
export interface LoginForm {
|
|
24
|
+
username: string
|
|
25
|
+
password: string
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// 商品表单类型
|
|
29
|
+
export interface ProductForm {
|
|
30
|
+
name: string
|
|
31
|
+
price: number
|
|
32
|
+
description: string
|
|
33
|
+
image: string
|
|
34
|
+
category: string
|
|
35
|
+
stock: number
|
|
36
|
+
status: 'on' | 'off'
|
|
37
|
+
}
|
|
@@ -63,8 +63,8 @@ import { genMenus, getMenuIcons } from '@/menus.jsx'
|
|
|
63
63
|
import { router } from '@/router.js'
|
|
64
64
|
import { getWord, i18nByKey, appData, appFn, useApiMode, useTemplateMode } from '@/tools'
|
|
65
65
|
import useTheme from '@/tools/useTheme'
|
|
66
|
-
import FloatSettings from '@/
|
|
67
|
-
import VersionTip from '@/
|
|
66
|
+
import FloatSettings from '@/components/float-settings.vue'
|
|
67
|
+
import VersionTip from '@/components/version-tip.vue'
|
|
68
68
|
|
|
69
69
|
export default defineComponent({
|
|
70
70
|
name: 'LayoutVue',
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|