Compare commits

..

19 Commits
HEAD ... fang

Author SHA1 Message Date
063a5af822 add right drawer 2023-09-08 03:52:18 +00:00
cccff1d16d fang create 2023-09-06 13:02:40 +00:00
7a9718a6fa flow function add 2023-08-09 13:36:09 +00:00
f597f7aa5a Merge branch 'mvp-step1' of https://dev.azure.com/alicorn-dev/KintoneAppBuilder/_git/KintoneAppBuilder into mvp-step1 2023-08-02 11:35:51 +00:00
0ec2b22754 App,Field,Action Select Dialogue 2023-08-02 11:35:32 +00:00
a04f7b1bd5 Updated README.md 2023-07-31 08:51:25 +00:00
2240603c2c Renamed VUE3.0コーディングルール.md to VUE3.0-coding-rule.md 2023-07-31 08:50:35 +00:00
d9a7532805 Updated README.md 2023-07-30 18:22:40 +00:00
e59f9b802b Renamed VUE3.0编程规范.md to VUE3.0コーディングルール.md 2023-07-30 18:21:51 +00:00
ad1c330231 Renamed vue3.0编程概要.md to vue3.0概要.md 2023-07-30 18:21:30 +00:00
a1905a1274 VUEコーディングルール追加 2023-07-31 03:14:31 +09:00
e515f99a44 slot test 2023-07-30 10:02:21 +00:00
d42fac9a7d component added 2023-07-30 12:35:35 +09:00
da3df6f0a7 Merge branch 'mvp-step1' of https://dev.azure.com/alicorn-dev/KintoneAppBuilder/_git/KintoneAppBuilder into mvp-step1 2023-07-29 22:42:09 +09:00
0bf3a1b2c8 document 追加 2023-07-29 22:41:41 +09:00
b63999c7f9 Add Dialogue 2023-07-29 13:40:36 +00:00
772ab3c6a5 Add Dialogue 2023-07-29 13:40:04 +00:00
e3c66a5bc4 document追加 2023-07-28 15:02:11 +09:00
9e510b0183 CORS追加 2023-07-26 23:51:44 +09:00
25 changed files with 3168 additions and 35 deletions

View File

@@ -27,5 +27,5 @@ class Kintone(Base):
id = Column(Integer, primary_key=True, index=True)
type = Column(Integer, index=True, nullable=False)
name = Column(String(100), nullable=False)
desc = Column(String(500))
content = Column(String(2000))
desc = Column(String)
content = Column(String)

View File

@@ -10,7 +10,7 @@ from app.db import Base,engine
from app.core.auth import get_current_active_user
from app.core.celery_app import celery_app
from app import tasks
from fastapi.middleware.cors import CORSMiddleware
Base.metadata.create_all(bind=engine)
@@ -18,6 +18,19 @@ app = FastAPI(
title=config.PROJECT_NAME, docs_url="/api/docs", openapi_url="/api"
)
origins = [
"http://localhost:9000",
"http://localhost",
"http://localhost:8080",
]
app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# @app.middleware("http")
# async def db_session_middleware(request: Request, call_next):

Binary file not shown.

View File

File diff suppressed because one or more lines are too long

View File

@@ -47,3 +47,7 @@ quasar build
### Customize the configuration
See [Configuring quasar.config.js](https://v2.quasar.dev/quasar-cli-vite/quasar-config-js).
## VUE3.0编程规范
1. [VUE3.0编程概要](./VUE3.0概要.md)
2. [VUE3.0编程规范](./VUE3.0-coding-rule.md)

View File

@@ -0,0 +1,241 @@
以下是一份 Vue 3 和 TypeScript 的编程规范:
**1. 组件定义**
- 推荐使用 `defineComponent` 来定义组件,以获取 TypeScript 的类型支持。
```typescript
import { defineComponent } from 'vue'
export default defineComponent({
// 组件选项
})
```
**2. 数据定义**
- 使用 `reactive` 定义响应式对象。
- 使用 `ref` 定义响应式单值。
- 使用 `computed` 定义计算属性。
```typescript
import { reactive, ref, computed } from 'vue'
const state = reactive({
count: 0,
message: 'Hello Vue 3'
})
const count = ref(0)
const doubledCount = computed(() => state.count * 2)
```
**3. 生命周期钩子**
- 使用 `onMounted``onUpdated` 等生命周期钩子,而不是 `beforeCreate``created``beforeMount``mounted``beforeUpdate``updated``beforeUnmount``unmounted`
```typescript
import { onMounted } from 'vue'
onMounted(() => {
console.log('Component is mounted.')
})
```
**4. 组件通信**
- 使用 `props``emit` 实现父子组件通信。
- 使用 `provide``inject` 实现祖先和后代组件通信。
- 不再推荐使用 `event bus` 进行任意组件间的通信,可以使用 Vuex 或者全局 `provide`/`inject` 替代。
```typescript
// 父组件
<ChildComponent @my-event="handleEvent" />
// 子组件
this.$emit('my-event', eventData)
```
**5. 异步处理**
- 使用 `async/await` 进行异步处理。
- 使用 `Suspense``async setup()` 处理异步依赖。
```typescript
import { ref, onMounted } from 'vue'
import axios from 'axios'
onMounted(async () => {
const response = await axios.get('https://api.example.com/data')
data.value = response.data
})
```
**6. Vue Router 和 Vuex**
- 使用 Vue Router 4 和 Vuex 4它们是为 Vue 3 重新设计的。
- 使用 `useRouter``useRoute` 钩子函数在组件中使用 router。
- 使用 `useStore` 钩子函数在组件中使用 Vuex store。
```typescript
import { useStore } from 'vuex'
import { useRouter } from 'vue-router'
const store = useStore()
const router = useRouter()
```
**7. 组件模板**
- 使用 `v-model` 代替 `.sync` 修饰符进行双向绑定。
- 使用 `v-for``:key` 渲染列表。
- 使用 `v-if``v-else``v-else-if` 进行条件渲染。
- 使用 `v-on` 或者 `@` 监听事件。
- 使用 `v-bind` 或者 `:` 绑定属性。
```html
<template>
<div v-if="condition">If block</div>
<div v-else>Else block</div>
<ul>
<li v-for="(item, index) in list" :key="index">{{ item }}</li>
</ul>
<button @click="handleClick">Click me</button>
</template>
```
8. **使用类型注解和接口**
在 TypeScript 中,尽可能使用类型注解和接口来提供更完善的类型信息和类型检查。这将有助于发现和预防错误。
```typescript
interface User {
name: string;
age: number;
}
const user: User = {
name: 'John',
age: 30
}
```
9. **模块化和组件化**
尽可能将功能和逻辑模块化和组件化,使得代码更易于理解和维护。特别是使用 Composition API 时,可以将公共的逻辑封装成 composable 函数。
下面是一个使用 `Suspense` 和 `axios` 的示例,这个示例将会从一个 JSON Placeholder API 获取数据:
首先,我们创建一个 composable 函数,用于获取数据:
```typescript
import { ref } from 'vue'
import axios from 'axios'
export function useAsyncData(url: string) {
const data = ref(null)
const error = ref(null)
const fetchData = async () => {
try {
const response = await axios.get(url)
data.value = response.data
} catch (e) {
error.value = e
}
}
return { data, error, fetchData }
}
```
然后,我们创建一个 Vue 组件来使用这个函数:
```js
<template>
<Suspense>
<template #default>
<div v-if="error">{{ error.message }}</div>
<div v-else>{{ data }}</div>
</template>
<template #fallback>
<div>Loading...</div>
</template>
</Suspense>
</template>
<script lang="ts">
import { defineComponent, onMounted } from 'vue'
import { useAsyncData } from './composables/useAsyncData'
export default defineComponent({
setup() {
const { data, error, fetchData } = useAsyncData('https://jsonplaceholder.typicode.com/posts/1')
onMounted(fetchData)
return { data, error }
},
})
</script>
<style scoped>
/* 在这里添加 CSS 样式 */
</style>
```
在这个示例中,我们在 `useAsyncData` 函数中获取数据。当 `onMounted` 钩子函数被调用时(也就是当组件挂载完成后),我们开始获取数据。这个过程是异步的,因此我们在 `Suspense` 组件的 `#fallback` 插槽中显示一个 "Loading..." 的提示。一旦数据加载完成,`Suspense` 组件的 `#default` 插槽就会被渲染,并显示获取到的数据。
10. **良好的代码风格**
遵循一致的代码风格和代码质量规则,例如使用 ESLint 和 Prettier 来检查和格式化代码。
11. **单元测试和端到端测试**
对关键的组件和函数编写单元测试,对用户的主要操作路径编写端到端测试,确保功能的正确性。
12. **注释和文档**
对复杂的逻辑和函数编写注释,提供必要的项目文档,帮助其他开发者理解和使用你的代码。
13. **以下是 Vue 2 中被废弃或改变的部分API以及在 Vue 3 中的替代方案:**
| Vue 2.x | Vue 3.0 | 说明 |
| --- | --- | --- |
| `Vue.set` / `this.$set` | 响应式数据现在是默认的,无需使用这些方法 | Vue 3 的响应式系统是从头开始构建的,所有的对象和数组都是响应式的 |
| `Vue.delete` / `this.$delete` | 无需使用这些方法 | 在 Vue 3 中,你只需要使用 `delete` 操作符即可 |
| `filters` | 无对应项 | Vue 3 不再支持过滤器,建议使用计算属性或方法替代 |
| `Vue.observable` | `reactive` | 用 `reactive` 替代 `Vue.observable`,实现数据的响应式 |
| `Vue.prototype` | `app.config.globalProperties` | 在 Vue 3 中,全局 API 已经改变,`Vue.prototype` 被 `app.config.globalProperties` 替代 |
| `Vue.component`, `Vue.directive`, `Vue.mixin`, `Vue.use` | `app.component`, `app.directive`, `app.mixin`, `app.use` | 全局注册的 API 改变,如 `Vue.component` 变为 `app.component` |
| `beforeDestroy` 和 `destroyed` 生命周期钩子 | `beforeUnmount` 和 `unmounted` | 生命周期钩子名字变化,`beforeDestroy` 和 `destroyed` 分别改为 `beforeUnmount` 和 `unmounted` |
| `$on`, `$off`, `$once` | 无对应项 | Event Bus方法被移除需要用户自行实现或者使用第三方库 |
| `v-model` 在自定义组件上使用 | 需要明确的 `modelValue` 和 `update:modelValue` | Vue 3 对 `v-model` 的改动使其在自定义组件上更具灵活性 |
| `functional` 选项 | 无对应项 | Vue 3 不再支持函数式组件的写法,而是推荐使用 `render` 函数或 `setup` 函数 |
| 异步组件的 `functional` 写法 | `defineAsyncComponent` | 异步组件的创建方式更改,通过 `defineAsyncComponent` 方法创建 |
| `destroyed` 和 `beforeDestroy` 钩子函数 | `unmounted` 和 `beforeUnmount` | 生命周期钩子的名称已改为更直观的名称,以更好地表示其在组件实例生命周期中的角色 |
| `Vue.extend` | `defineComponent` | Vue 3 使用 `defineComponent` 方法定义组件,有更好的类型推断 |
注意Vue 3 对于 Options API 和 Composition API 提供了完全的支持,你可以在一个组件中混合使用这两种 API。不过为了代码的一致性和可读性建议在一个项目中选择一种 API 并坚持使用。
- **以下是 Vue 3.0 中的 Composition API 函数的基本说明,以及与 Options API 的对比**
| Composition API | 说明 | 对应的 Options API |
| --------------- | ---- | ------------------ |
| `setup` | `setup` 是一个新引入的组件选项,用于使用 Composition API。它是组件内部使用 Composition API 的入口。| 无 |
| `ref` | `ref` 函数用于创建一个响应式的数据。它接收一个参数,返回一个响应式的 Ref 对象。| `data` |
| `reactive` | `reactive` 函数用于创建一个响应式的对象。它接收一个普通对象,返回一个响应式的对象。| `data` |
| `computed` | `computed` 函数用于创建一个计算属性。它接收一个 getter 函数或者一个具有 getter 和 setter 的对象,返回一个响应式的 Ref 对象。| `computed` |
| `watch` | `watch` 函数用于响应式地跟踪和触发副作用。它接收一个响应式的源和一个执行副作用的回调函数。| `watch` |
| `watchEffect` | `watchEffect` 函数用于立即执行传入的一个函数,并响应式地追踪其依赖,并在其依赖变更时重新运行该函数。 | 无 |
| `onMounted` | `onMounted` 函数在组件被挂载时调用。它接收一个在组件挂载后执行的回调函数。 | `mounted` |
| `onUnmounted` | `onUnmounted` 函数在组件被卸载时调用。它接收一个在组件卸载后执行的回调函数。 | `beforeDestroy`/`unmounted` |
| `onUpdated` | `onUpdated` 在组件更新后调用。它接收一个在组件更新后执行的回调函数。| `updated` |
| `provide` | `provide` 函数用于在组件上定义一个可以被后代组件注入的值。它接收一个提供的键和值。 | 有,与 `provide/inject` 相似但是属性而不是函数 |
| `inject` | `inject` 函数用于在组件中注入一个由祖先组件提供的值。它接收一个注入的键。 | 有,但与 `provide/inject` 相似但是属性而不是函数 |
值得注意的是,虽然一些 Composition API 函数与 Options API 的某些选项有相似之处,但它们的工作方式和使用方式可能有所不同。在实际使用中,你需要根据具体的使用场景和需求选择合适的 API。

View File

View File

@@ -0,0 +1,40 @@
<template>
<div class="q-pa-md">
<q-table :title="name+'一覧'" row-key="name" :selection="type" v-model:selected="selected" :columns="columns" :rows="rows" />
</div>
</template>
<script>
import { ref,onMounted,reactive } from 'vue'
import { api } from 'boot/axios';
export default {
name: 'actionSelect',
props: {
name: String,
type: String
},
setup() {
const columns = [
{ name: 'name', required: true,label: 'アクション名',align: 'left',field: 'name',sortable: true},
{ name: 'desc', align: 'left', label: '説明', field: 'desc', sortable: true },
{ name: 'content', label: '内容', field: 'content', sortable: true }
]
const rows = reactive([])
onMounted(async () => {
await api.get('http://127.0.0.1:8000/api/kintone/2').then(res =>{
res.data.forEach((item) =>
{
rows.push({name:item.name,desc:item.desc,content:item.content});
}
)
});
});
return {
columns,
rows,
selected: ref([]),
}
},
}
</script>

View File

@@ -0,0 +1,83 @@
<template>
<div id="action1">
<q-card class="my-card">
<q-card-section class="bg-primary text-white">
<div class="text-h6">Our Changing Planet</div>
<div class="text-subtitle2">by John Doe</div>
</q-card-section>
<q-separator />
<q-card-actions align="right">
<q-btn flat>設定</q-btn>
<q-btn class="del" flat @click="clickdel">削除</q-btn>
</q-card-actions>
</q-card>
<div class="next" style="display: table;width: 100%;height:40px;" @mouseenter="showAdd = true" @mouseleave="()=>{if(!showMenu) showAdd = false;}">
<div aria-hidden="false" style="display: table-row;">
<div
style="display: table-cell;background: url(&quot;data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAiIGhlaWdodD0iNDAiIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDEzIDMwIiB2ZXJzaW9uPSIxLjEiIHZpZXdCb3g9IjAgMCAyMCA0MCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4NCiA8c3R5bGUgdHlwZT0idGV4dC9jc3MiPi5zdDB7ZmlsbDpub25lO3N0cm9rZTojNUI1QjVDO3N0cm9rZS13aWR0aDoyO3N0cm9rZS1taXRlcmxpbWl0OjEwO30NCgkuc3Qxe2ZpbGw6IzVCNUI1Qzt9PC9zdHlsZT4NCiA8cmVjdCB4PSI5IiB3aWR0aD0iMiIgaGVpZ2h0PSIzNy45NzQiIGZpbGw9IiM1MTUxNTEiIHN0cm9rZS13aWR0aD0iNC41MDYyIi8+DQogPHBvbHlnb24gdHJhbnNmb3JtPSJtYXRyaXgoMS4xNzg1IDAgMCAxLjE3ODUgLS42MDY5MiAyMy41NDQpIiBwb2ludHM9IjEuOTI4IDQuMDY1IDguOTk5IDExLjEzNiAxNi4wNzIgNC4wNjUgMTcuNDg2IDUuNDc5IDguOTk5IDEzLjk2NCAwLjUxNSA1LjQ3OSIgZmlsbD0iIzUxNTE1MSIvPg0KPC9zdmc+DQo=&quot;) center center no-repeat;">
</div>
</div>
<div v-if="showAdd" style="display:table-row;height:inherit;position:absolute;left:50%;">
<div style="display:table-cell;">
<q-btn round size="xs" color="primary" label="+">
<q-menu v-model="showMenu">
<q-list style="min-width: 100px">
<q-item clickable v-close-popup>
<q-item-section @click="clickadd">New tab</q-item-section>
</q-item>
</q-list>
</q-menu>
</q-btn>
</div>
</div>
</div>
</div>
</template>
<script lang="ts">
import { ref,watch } from 'vue'
export default {
emits: [
'addaction'
],
setup(props,context) {
const showAdd = ref(false)
const showMenu = ref(false)
watch(showMenu,(newVal) =>{
console.log('3');
if(!newVal)
{
showAdd.value = false;
}
});
const clickadd = () => {
console.log('3');
context.emit('addaction');
//let oDiv1 = pdiv;
// let oDiv1 = document.getElementById('action1');
// let oDiv2 = document.createElement('div');
// if (oDiv1 !== null) {
// oDiv2.innerHTML = oDiv1?.innerHTML;
// oDiv1?.after(oDiv2);
// let oAdd = oDiv2.getElementsByClassName('next')[0];
// oAdd.addEventListener('mouseenter', mouseenter);
// oAdd.addEventListener('mouseleave', mouseleave);
// let oDel = oDiv2.getElementsByClassName('del')[0];
// oDel.addEventListener('click', clickdel);
// }
};
const clickdel = (event: Event) => {
let oBtn = event.target as Element;
oBtn.parentElement?.parentElement?.parentElement?.parentElement?.remove();
};
// window.clickadd = clickadd;
// window.clickdel = clickdel;
// window.mouseenter = mouseenter;
// window.mouseleave = mouseleave;
return {clickadd, clickdel, showAdd, showMenu }
}
}
</script>

View File

@@ -0,0 +1,41 @@
<template>
<div class="q-pa-md">
<q-table :title="name+'一覧'" :selection="type" v-model:selected="selected" :columns="columns" :rows="rows" />
</div>
</template>
<script>
import { ref,onMounted,reactive } from 'vue'
import { api } from 'boot/axios';
export default {
name: 'appSelect',
props: {
name: String,
type: String
},
setup() {
const columns = [
{ name: 'id', required: true,label: 'アプリID',align: 'left',field: 'id',sortable: true},
{ name: 'name', align: 'center', label: 'アプリ名', field: 'name', sortable: true },
{ name: 'creator', label: '作成者', field: 'creator', sortable: true },
{ name: 'createdate', label: '作成日時', field: 'createdate' }
]
const rows = reactive([])
onMounted( () => {
api.get('allapps').then(res =>{
res.data.apps.forEach((item) =>
{
rows.push({id:item.appId,name:item.name,creator:item.creator.name,createdate:item.createdAt});
}
)
});
});
return {
columns,
rows,
selected: ref([]),
}
},
}
</script>

View File

@@ -0,0 +1,47 @@
<template>
<div class="q-pa-md">
<q-table :title="name+'一覧'" row-key="name" :selection="type" v-model:selected="selected" :columns="columns" :rows="rows" />
</div>
</template>
<script>
import { ref,onMounted,reactive } from 'vue'
import { api } from 'boot/axios';
export default {
name: 'fieldSelect',
props: {
name: String,
type: String,
appId:Number
},
setup(props) {
const columns = [
{ name: 'name', required: true,label: 'フィールド名',align: 'left',field: row=>row.name,sortable: true},
{ name: 'code', label: 'フィールドコード', align: 'left',field: 'code', sortable: true },
{ name: 'type', label: 'フィールドタイプ', align: 'left',field: 'type', sortable: true }
]
const rows = reactive([])
onMounted( () => {
api.get('appfields', {
params:{
app: props.appId
}
}).then(res =>{
let fields = res.data.properties;
console.log(fields);
Object.keys(fields).forEach((key) =>
{
rows.push({name:fields[key].label,code:fields[key].code,type:fields[key].type});
}
)
});
});
return {
columns,
rows,
selected: ref([]),
}
},
}
</script>

View File

@@ -0,0 +1,42 @@
<template>
<div class="q-pa-md q-gutter-sm">
<q-dialog :model-value="visible" persistent>
<q-card style="min-width: 350px">
<q-card-section>
<div class="text-h6">{{ name }}選択</div>
</q-card-section>
<q-card-section class="q-pt-none">
<slot></slot>
</q-card-section>
<q-card-actions align="right" class="text-primary">
<q-btn flat label="確定" v-close-popup @click="CloseDialogue('OK')" />
<q-btn flat label="キャンセル" v-close-popup @click="CloseDialogue('Cancel')" />
</q-card-actions>
</q-card>
</q-dialog>
</div>
</template>
<script>
export default {
name: 'showDialog',
props: {
name:String,
visible: Boolean,
},
emits: [
'close'
],
setup(props, context) {
const CloseDialogue = (val) => {
context.emit('update:visible', false);
context.emit('close', val);
}
return {
CloseDialogue
}
},
}
</script>

View File

@@ -0,0 +1,57 @@
<template>
<div>
<div v-for="(item, index) in componentData" :key="index">
<component :is="item.component" v-bind="item.props" v-model="item.props.modelValue"></component>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import InputText from '../right/InputText.vue';
import SelectBox from '../right/SelectBox.vue';
import DatePicker from '../right/DatePicker.vue';
import FieldInput from '../right/FieldInput.vue';
export default defineComponent({
name: 'ActionProperty',
components: {
InputText,
SelectBox,
DatePicker,
FieldInput
},
props: {
jsonData: {
type: Object,
required: true,
},
jsonValue:{
type: Object,
required: false,
}
},
computed: {
componentData() {
return this.jsonData.elements.map((element: any) => {
if(this.jsonValue != undefined )
{
if(this.jsonValue.hasOwnProperty(element.props.name))
{
element.props.modelValue = this.jsonValue[element.props.name];
}
else
{
element.props.modelValue = '';
}
}
return {
component: element.component,
props: element.props,
};
});
},
},
});
</script>

View File

@@ -0,0 +1,45 @@
<template>
<q-input v-model="selectedDate" :label="placeholder" mask="date" :rules="['date']">
<template v-slot:append>
<q-icon name="event" class="cursor-pointer">
<q-popup-proxy cover transition-show="scale" transition-hide="scale">
<q-date v-model="selectedDate">
<div class="row items-center justify-end">
<q-btn v-close-popup label="Close" color="primary" flat />
</div>
</q-date>
</q-popup-proxy>
</q-icon>
</template>
</q-input>
</template>
<script lang="ts">
import { defineComponent, ref ,watchEffect} from 'vue';
export default defineComponent({
name: 'DatePicker',
props: {
placeholder: {
type: String,
default: '',
},
modelValue: {
type: String,
default: '',
},
},
setup(props, { emit }) {
const selectedDate = ref(props.modelValue);
watchEffect(() => {
emit('update:modelValue', selectedDate.value);
});
return {
selectedDate
};
}
});
</script>

View File

@@ -0,0 +1,62 @@
<template>
<q-input v-model="selectedField" :label="placeholder">
<template v-slot:append>
<q-icon name="search" class="cursor-pointer" @click="showDg"/>
</template>
</q-input>
<show-dialog v-model:visible="show" name="フィールド一覧" @close="closeDg">
<field-select ref="appDg" name="フィールド" type="single" :appId="1"></field-select>
</show-dialog>
</template>
<script lang="ts">
import { defineComponent, ref ,watchEffect} from 'vue';
import ShowDialog from '../ShowDialog.vue';
import FieldSelect from '../FieldSelect.vue';
export default defineComponent({
name: 'FieldInput',
components: {
ShowDialog,
FieldSelect,
},
props: {
placeholder: {
type: String,
default: '',
},
modelValue: {
type: String,
default: '',
},
},
setup(props, { emit }) {
const appDg = ref();
const show = ref(false);
const selectedField = ref(props.modelValue);
const showDg = () => {
show.value = true;
};
const closeDg = (val:string) => {
if (val == 'OK') {
selectedField.value = appDg.value.selected[0].name;
}
};
watchEffect(() => {
emit('update:modelValue', selectedField.value);
});
return {
appDg,
show,
showDg,
closeDg,
selectedField,
};
}
});
</script>

View File

@@ -0,0 +1,33 @@
<template>
<q-input :label="placeholder" v-model="inputValue"/>
</template>
<script lang="ts">
import { defineComponent,ref,watchEffect } from 'vue';
export default defineComponent({
name: 'InputText',
props: {
placeholder: {
type: String,
default: '',
},
modelValue: {
type: String,
default: '',
},
},
setup(props, { emit }) {
const inputValue = ref(props.modelValue);
watchEffect(() => {
emit('update:modelValue', inputValue.value);
});
return {
inputValue,
};
},
});
</script>

View File

@@ -0,0 +1,36 @@
<template>
<q-select v-model="selectedValue" :label="placeholder" :options="options"/>
</template>
<script lang="ts">
import { defineComponent,ref,watchEffect } from 'vue';
export default defineComponent({
name: 'SelectBox',
props: {
placeholder: {
type: String,
default: '',
},
options: {
type: Array,
required: true,
},
modelValue: {
type: String,
default: '',
},
},
setup(props, { emit }) {
const selectedValue = ref(props.modelValue);
watchEffect(() => {
emit('update:modelValue', selectedValue.value);
});
return {
selectedValue
};
},
});
</script>

View File

@@ -9,41 +9,67 @@
</div>
<div style="min-height: 100vh;">
<div class="q-pa-md">
<q-btn-dropdown
split
color="primary"
label="ルール新規作成"
size="lg"
>
<q-list>
<q-item
v-for="action in actions"
clickable v-close-popup
@click="onItemClick"
:key="action"
>
<q-item-section>
<q-item-label>{{ action }}</q-item-label>
</q-item-section>
</q-item>
</q-list>
</q-btn-dropdown>
<q-btn-dropdown split color="primary" label="ルール新規作成" size="lg">
<q-list>
<q-item v-for="action in actions" clickable v-close-popup @click="onItemClick" :key="action">
<q-item-section>
<q-item-label>{{ action }}</q-item-label>
</q-item-section>
</q-item>
</q-list>
</q-btn-dropdown>
</div>
<div class="q-pa-md">
<q-select v-model="model" :options="options" label="Standard"/>
<q-btn :label="model+'選択'" color="primary" @click="showDg()" />
<show-dialog v-model:visible="show" :name="model" @close="closeDg">
<template v-if="model=='アプリ'">
<app-select ref="appDg" :name="model" type="single"></app-select>
</template>
<template v-if="model=='フィールド'">
<field-select ref="appDg" :name="model" type="multiple" :appId="1"></field-select>
</template>
<template v-if="model=='アクション'">
<action-select ref="appDg" :name="model" type="single"></action-select>
</template>
</show-dialog>
</div>
</div>
</div>
</q-page>
</template>
<script setup lang="ts">
import ShowDialog from 'components/ShowDialog.vue';
import AppSelect from 'components/AppSelect.vue';
import FieldSelect from 'components/FieldSelect.vue';
import ActionSelect from 'components/ActionSelect.vue';
import { ref } from 'vue'
let show = ref(false);
let appDg = ref();
let model = ref('アプリ');
let options = ['アプリ','フィールド','アクション']
const showDg = () => {
show.value = true;
};
const closeDg = (val:string) => {
if (val == 'OK') {
alert(JSON.stringify(appDg.value.selected))
}
};
interface Props {
title: string;
actions:string[];
actions: string[];
}
const props = withDefaults(defineProps<Props>(), {
title:"ルールエディター",
actions:()=>["フィールド制御","一覧画面","その他"]
title: "ルールエディター",
actions: () => ["フィールド制御", "一覧画面", "その他"]
});
function onItemClick(evt: Event){
return;
function onItemClick(evt: Event) {
return;
}
</script>

View File

@@ -0,0 +1,96 @@
<template>
<div class="q-pa-md q-gutter-sm">
<q-btn label="プロパティ" icon="keyboard_arrow_right" color="primary" @click="open('right')" />
<!-- <q-btn label="Readプロパティ" icon="keyboard_arrow_right" color="primary" @click="write('right')" /> -->
<q-dialog v-model="dialog" :position="position">
<q-card class="column full-height" style="width: 300px">
<q-card-section>
<div class="text-h6">プロパティ</div>
</q-card-section>
<q-card-section class="col q-pt-none">
<ActionProperty :jsonData="jsonData" :jsonValue="jsonValue"/>
</q-card-section>
<q-card-actions align="right" class="bg-white text-teal">
<q-btn flat label="Save" v-close-popup @click="save"/>
<q-btn flat label="Cancel" v-close-popup />
</q-card-actions>
</q-card>
</q-dialog>
</div>
</template>
<script setup lang="ts">
import { ref,onMounted } from 'vue'
import ActionProperty from 'components/right/ActionProperty.vue';
const dialog = ref(false)
const position = ref('top')
const jsonData = {
elements: [
{
component: 'InputText',
props: {
name:'1',
placeholder: 'Enter some text',
modelValue: '',
},
},
{
component: 'SelectBox',
props: {
name:'2',
placeholder: 'Choose an option',
modelValue: '',
options: [
'option1',
'option2',
'option3'
],
},
},
{
component: 'DatePicker',
props: {
name:'3',
placeholder: 'Choose a date',
modelValue: '',
},
},
{
component: 'FieldInput',
props: {
name:'4',
placeholder: 'Choose a field',
modelValue: '',
},
},
]
};
let jsonValue = {
1:'abc',
2:'option2',
3:'2023/09/04',
4:'6666'
};
const open = (pos:string) => {
position.value = pos
dialog.value = true
};
const save = async () =>{
jsonData.elements.forEach(property => {
if(jsonValue != undefined)
{
jsonValue[property.props.name] = property.props.modelValue;
}
});
console.log(jsonValue);
}
</script>

View File

@@ -1,13 +1,88 @@
<template>
<div>
<q-page>
</div>
<div class="q-pa-md column content-center items-center">
<div>
<q-btn label="アクション選択" color="primary" @click="showDg()" v-if="addshow" />
<show-dialog v-model:visible="show" name="アクション" @close="closeDg">
<action-select ref="appDg" name="アクション" type="single"></action-select>
</show-dialog>
</div>
<div id="action"></div>
</div>
</q-page>
</template>
<script>
export default {
<script setup lang="ts">
import { ref } from 'vue';
import ShowDialog from 'components/ShowDialog.vue';
import ActionSelect from 'components/ActionSelect.vue';
const show = ref(false);
const addshow = ref(true);
const appDg = ref();
const showDg = () => {
show.value = true;
};
const closeDg = (val: string) => {
if (val == 'OK') {
//alert(JSON.stringify(appDg.value.selected[0].content));
let oDiv1 = document.getElementById('action');
let oDiv2 = document.createElement('div');
oDiv2.setAttribute('class', 'action');
if (oDiv1 !== null) {
oDiv2.innerHTML = appDg.value.selected[0].content;
oDiv1?.append(oDiv2);
// let oAdd = oDiv2.getElementsByClassName('next')[0];
// oAdd.addEventListener('mouseenter', mouseenter);
// oAdd.addEventListener('mouseleave', mouseleave);
// let oDel = oDiv2.getElementsByClassName('del')[0];
// oDel.addEventListener('click', clickdel);
}
if (document.getElementsByClassName('action').length > 0) {
addshow.value = false;
}
};
};
const mouseenter = (event: Event) => {
console.log('1');
let oAdd = "<div style='display: table-cell;'><button type='button' onclick='clickadd(this.parentElement.parentElement.parentElement.parentElement)'>+</button></div>";
let oDiv1 = event.target as Element
let oDivs = oDiv1?.getElementsByClassName('add');
if (oDivs.length === 0) {
let oDiv2 = document.createElement('div');
oDiv2.className = "add";
oDiv2.setAttribute("style", "display:table-row;height:inherit;position: absolute;left:calc(50% - 19px);");
oDiv2.innerHTML = oAdd;
oDiv1?.append(oDiv2);
}
};
const mouseleave = (event: Event) => {
console.log('2');
let oDiv2 = (event.target as Element)?.parentElement?.getElementsByClassName('add');
oDiv2[0]?.remove();
};
const clickadd = (pdiv: Element) => {
console.log('3');
show.value = true;
};
const clickdel = (pdiv: Element) => {
pdiv.remove();
if (document.getElementsByClassName('action').length == 0) {
addshow.value = true;
}
};
window.clickadd = clickadd;
window.clickdel = clickdel;
window.mouseenter = mouseenter;
window.mouseleave = mouseleave;
}
</script>
<style lang="scss">
</style>

View File

@@ -0,0 +1,101 @@
<template>
<div class="q-pa-md q-gutter-sm">
<q-btn label="プロパティ" icon="keyboard_arrow_right" color="primary" @click="drawerRight = !drawerRight" />
<!-- <q-btn label="Readプロパティ" icon="keyboard_arrow_right" color="primary" @click="write('right')" /> -->
<q-drawer
side="right"
v-model="drawerRight"
show-if-above
bordered
:width="301"
:breakpoint="500"
class="bg-grey-3"
>
<q-card class="column full-height" style="width: 300px">
<q-card-section>
<div class="text-h6">プロパティ</div>
</q-card-section>
<q-card-section class="col q-pt-none">
<ActionProperty :jsonData="jsonData" :jsonValue="jsonValue" v-if="drawerRight"/>
</q-card-section>
<q-card-actions align="right" class="bg-white text-teal">
<q-btn flat label="Save" @click="save"/>
<q-btn flat label="Cancel" @click="cancel" />
</q-card-actions>
</q-card>
</q-drawer>
</div>
</template>
<script setup lang="ts">
import { reactive, ref } from 'vue'
import ActionProperty from 'components/right/ActionProperty.vue';
const drawerRight = ref(false);
const jsonData = {
elements: [
{
component: 'InputText',
props: {
name:'1',
placeholder: 'Enter some text',
modelValue: '',
},
},
{
component: 'SelectBox',
props: {
name:'2',
placeholder: 'Choose an option',
modelValue: '',
options: [
'option1',
'option2',
'option3'
],
},
},
{
component: 'DatePicker',
props: {
name:'3',
placeholder: 'Choose a date',
modelValue: '',
},
},
{
component: 'FieldInput',
props: {
name:'4',
placeholder: 'Choose a field',
modelValue: '',
},
},
]
};
let jsonValue = {
1:'abc',
2:'option2',
3:'2023/09/04',
4:'6666'
};
const cancel = async() =>{
drawerRight.value = false;
}
const save = async () =>{
jsonData.elements.forEach(property => {
if(jsonValue != undefined)
{
jsonValue[property.props.name] = property.props.modelValue;
}
});
console.log(jsonValue);
drawerRight.value=false;
}
</script>

View File

@@ -11,6 +11,22 @@ const routes: RouteRecordRaw[] = [
component: () => import('layouts/MainLayout.vue'),
children: [{ path: '', component: () => import('pages/RuleEditor.vue') }],
},
{
path: '/test/',
component: () => import('layouts/MainLayout.vue'),
children: [{ path: '', component: () => import('pages/testQursar.vue') }],
},
,
{
path: '/flow/',
component: () => import('layouts/MainLayout.vue'),
children: [{ path: '', component: () => import('pages/testFlow.vue') }],
},
{
path: '/right/',
component: () => import('layouts/MainLayout.vue'),
children: [{ path: '', component: () => import('pages/testRight.vue') }],
},
// Always leave this as last one,
// but you can also remove it

1075
frontend/vue3.0概要.md Normal file

File diff suppressed because it is too large Load Diff