Compare commits

...

28 Commits

Author SHA1 Message Date
76457b6667 UserDomain Add 2023-10-09 15:55:58 +09:00
e1f2afa942 sqlserver->postgresql 2023-09-30 15:16:05 +09:00
8d5dff60f1 Merge branch 'maxz-real-impl' into mvp_step2_dev 2023-09-30 13:33:39 +09:00
461cd26690 add app dialog 2023-09-30 13:12:08 +09:00
4c6b2ea844 DB連動実装 2023-09-27 14:35:24 +09:00
418f45f997 Merge branch 'maxz-pinia' of https://dev.azure.com/alicorn-dev/KintoneAppBuilder/_git/KintoneAppBuilder into maxz-pinia 2023-09-24 00:12:49 +09:00
51ebe99d1c FlowEditorPage2 2023-09-24 00:12:42 +09:00
2f1f8a60fc Merge branch 'maxz-pinia' of https://dev.azure.com/alicorn-dev/KintoneAppBuilder/_git/KintoneAppBuilder into maxz-pinia 2023-09-23 23:56:01 +09:00
64795a80c7 actiontypes bug fix 2023-09-23 23:55:52 +09:00
94a17073dd flow add&update 2023-09-23 14:53:48 +00:00
7f7d625fdd backend merge 2023-09-23 06:38:00 +00:00
6902079866 add right panel with pinia 2023-09-23 15:19:53 +09:00
6aa057e590 flow api add 2023-09-21 07:22:12 +00:00
2721cd60d1 action bugfix 2023-09-18 02:54:53 +00:00
1f8d079d4d action bugfix 2023-09-18 02:54:35 +00:00
dt
f34dec1054 Merge branch 'daitian' of https://alicorn-dev@dev.azure.com/alicorn-dev/KintoneAppBuilder/_git/KintoneAppBuilder into daitian-pinia 2023-09-17 23:40:51 +08:00
dt
01b64f1aba Change App Selection Component 2023-09-17 23:34:24 +08:00
dt
3367ada343 Merge branch 'daitian' of https://alicorn-dev@dev.azure.com/alicorn-dev/KintoneAppBuilder/_git/KintoneAppBuilder into daitian-pinia 2023-09-17 21:46:21 +08:00
dt
f4ea3eaccb Merge branch 'maxz-new-step' of https://alicorn-dev@dev.azure.com/alicorn-dev/KintoneAppBuilder/_git/KintoneAppBuilder into daitian 2023-09-17 21:45:34 +08:00
dt
4adb8401d6 add pinia 2023-09-17 21:44:28 +08:00
3ae685a0e2 action add subtitle 2023-09-17 08:54:54 +00:00
dt
fce56e43c3 add pinia 2023-09-16 11:07:32 +08:00
dt
42618602f4 Replace ItemSelector with ActionSelec 2023-09-16 10:48:08 +08:00
c1e50736e8 action table add 2023-09-16 01:52:15 +00:00
dt
e02131846b selected change 2023-09-15 07:02:29 +08:00
dt
2c3b27d9de style change 2023-09-15 04:04:48 +08:00
dt
6ccc833f7d Remove unnecessary components, add an action bar. 2023-09-15 03:56:17 +08:00
dt
a0ecc2eee3 flow editor assembly and modification 2023-09-13 16:13:30 +08:00
40 changed files with 2061 additions and 201 deletions

View File

@@ -2,7 +2,8 @@ from fastapi import Request,Depends, APIRouter, UploadFile,HTTPException,File
from app.db import Base,engine from app.db import Base,engine
from app.db.session import get_db from app.db.session import get_db
from app.db.crud import * from app.db.crud import *
from app.db.schemas import AppBase, AppEdit, App,Kintone from app.db.schemas import *
from typing import List
platform_router = r = APIRouter() platform_router = r = APIRouter()
@@ -65,3 +66,121 @@ async def kintone_data(
): ):
kintone = get_kintones(db, type) kintone = get_kintones(db, type)
return kintone return kintone
@r.get(
"/actions",
response_model=t.List[Action],
response_model_exclude={"id"},
response_model_exclude_none=True,
)
async def action_data(
request: Request,
db=Depends(get_db),
):
actions = get_actions(db)
return actions
@r.get(
"/flow/{flowid}",
response_model=Flow,
response_model_exclude_none=True,
)
async def flow_details(
request: Request,
flowid: str,
db=Depends(get_db),
):
app = get_flow(db, flowid)
return app
@r.get(
"/flows/{appid}",
response_model=List[Flow],
response_model_exclude_none=True,
)
async def flow_list(
request: Request,
appid: str,
db=Depends(get_db),
):
flows = get_flows_by_app(db, appid)
return flows
@r.post("/flow", response_model=Flow, response_model_exclude_none=True)
async def flow_create(
request: Request,
flow: FlowBase,
db=Depends(get_db),
):
return create_flow(db, flow)
@r.put(
"/flow/{flowid}", response_model=Flow, response_model_exclude_none=True
)
async def flow_edit(
request: Request,
flow: FlowBase,
db=Depends(get_db),
):
return edit_flow(db, flow)
@r.delete(
"/flow/{flowid}", response_model=Flow, response_model_exclude_none=True
)
async def flow_delete(
request: Request,
flowid: str,
db=Depends(get_db),
):
return delete_flow(db, flowid)
@r.get(
"/domain/{userid}",
response_model=List[Domain],
response_model_exclude_none=True,
)
async def domain_details(
request: Request,
userid: str,
db=Depends(get_db),
):
domains = get_domain(db, userid)
return domains
@r.post("/domain", response_model=Domain, response_model_exclude_none=True)
async def domain_create(
request: Request,
domain: DomainBase,
db=Depends(get_db),
):
return create_domain(db, domain)
@r.put(
"/domain", response_model=Domain, response_model_exclude_none=True
)
async def domain_edit(
request: Request,
domain: DomainBase,
db=Depends(get_db),
):
return edit_domain(db, domain)
@r.delete(
"/domain/{userid}/{id}", response_model=Domain, response_model_exclude_none=True
)
async def domain_delete(
request: Request,
userid: int,
id: int,
db=Depends(get_db),
):
return delete_domain(db, userid,id)

View File

@@ -2,7 +2,7 @@ import os
PROJECT_NAME = "KintoneAppBuilder" PROJECT_NAME = "KintoneAppBuilder"
SQLALCHEMY_DATABASE_URI = "mssql+pymssql://maxz64@maxzdb:m@xz1205@maxzdb.database.windows.net/alloc" SQLALCHEMY_DATABASE_URI = "postgres://maxz64:m@xz1205@alicornkintone.postgres.database.azure.com/postgres"
BASE_URL = "https://mfu07rkgnb7c.cybozu.com" BASE_URL = "https://mfu07rkgnb7c.cybozu.com"

View File

@@ -116,3 +116,112 @@ def get_kintones(db: Session, type: int):
if not kintones: if not kintones:
raise HTTPException(status_code=404, detail="Data not found") raise HTTPException(status_code=404, detail="Data not found")
return kintones return kintones
def get_actions(db: Session):
actions = db.query(models.Action).all()
if not actions:
raise HTTPException(status_code=404, detail="Data not found")
return actions
def create_flow(db: Session, flow: schemas.FlowBase):
db_flow = models.Flow(
flowid=flow.flowid,
appid=flow.appid,
eventid=flow.eventid,
name=flow.name,
content=flow.content
)
db.add(db_flow)
db.commit()
db.refresh(db_flow)
return db_flow
def delete_flow(db: Session, flowid: str):
flow = get_flow(db, flowid)
if not flow:
raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Flow not found")
db.delete(flow)
db.commit()
return flow
def edit_flow(
db: Session, flow: schemas.FlowBase
) -> schemas.Flow:
db_flow = get_flow(db, flow.flowid)
if not db_flow:
raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Flow not found")
update_data = flow.dict(exclude_unset=True)
for key, value in update_data.items():
setattr(db_flow, key, value)
db.add(db_flow)
db.commit()
db.refresh(db_flow)
return db_flow
def get_flows(db: Session, flowid: str):
flows = db.query(models.Flow).all()
if not flows:
raise HTTPException(status_code=404, detail="Data not found")
return flows
def get_flow(db: Session, flowid: str):
flow = db.query(models.Flow).filter(models.Flow.flowid == flowid).first()
if not flow:
raise HTTPException(status_code=404, detail="Data not found")
return flow
def get_flows_by_app(db: Session, appid: str):
flows = db.query(models.Flow).filter(models.Flow.appid == appid).all()
if not flows:
raise HTTPException(status_code=404, detail="Data not found")
return flows
def create_domain(db: Session, domain: schemas.DomainBase):
db_domain = models.UserDomain(
userid=domain.userid,
name=domain.name,
url=domain.url,
kintoneuser=domain.kintoneuser,
kintonepwd=domain.kintonepwd
)
db.add(db_domain)
db.commit()
db.refresh(db_domain)
return db_domain
def delete_domain(db: Session, userid: int,id: int):
db_domain = db.query(models.UserDomain).get(id)
if not db_domain or db_domain.userid != userid:
raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Domain not found")
db.delete(db_domain)
db.commit()
return db_domain
def edit_domain(
db: Session, domain: schemas.DomainBase
) -> schemas.Domain:
db_domain = db.query(models.UserDomain).get(domain.id)
if not db_domain or db_domain.userid != domain.userid:
raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Domain not found")
update_data = domain.dict(exclude_unset=True)
for key, value in update_data.items():
if(key != "id"):
setattr(db_domain, key, value)
db.add(db_domain)
db.commit()
db.refresh(db_domain)
return db_domain
def get_domain(db: Session, userid: str):
domains = db.query(models.UserDomain).filter(models.UserDomain.userid == userid).all()
if not domains:
raise HTTPException(status_code=404, detail="Data not found")
return domains

View File

@@ -1,12 +1,16 @@
from sqlalchemy import Boolean, Column, Integer, String from sqlalchemy import Boolean, Column, Integer, String, DateTime,ForeignKey
from sqlalchemy.ext.declarative import as_declarative
from .session import Base from datetime import datetime
@as_declarative()
class Base:
id = Column(Integer, primary_key=True, index=True)
create_time = Column(DateTime, default=datetime.now)
update_time = Column(DateTime, default=datetime.now, onupdate=datetime.now)
class User(Base): class User(Base):
__tablename__ = "user" __tablename__ = "user"
id = Column(Integer, primary_key=True, index=True)
email = Column(String(50), unique=True, index=True, nullable=False) email = Column(String(50), unique=True, index=True, nullable=False)
first_name = Column(String(100)) first_name = Column(String(100))
last_name = Column(String(100)) last_name = Column(String(100))
@@ -17,15 +21,41 @@ class User(Base):
class AppSetting(Base): class AppSetting(Base):
__tablename__ = "appsetting" __tablename__ = "appsetting"
id = Column(Integer, primary_key=True, index=True)
appid = Column(String(100), index=True, nullable=False) appid = Column(String(100), index=True, nullable=False)
setting = Column(String(1000)) setting = Column(String(1000))
class Kintone(Base): class Kintone(Base):
__tablename__ = "kintone" __tablename__ = "kintone"
id = Column(Integer, primary_key=True, index=True)
type = Column(Integer, index=True, nullable=False) type = Column(Integer, index=True, nullable=False)
name = Column(String(100), nullable=False) name = Column(String(100), nullable=False)
desc = Column(String) desc = Column(String)
content = Column(String) content = Column(String)
class Action(Base):
__tablename__ = "action"
name = Column(String(100), index=True, nullable=False)
title = Column(String(200))
subtitle = Column(String(500))
outputpoints = Column(String)
property = Column(String)
class Flow(Base):
__tablename__ = "flow"
flowid = Column(String(100), index=True, nullable=False)
appid = Column(String(100), index=True, nullable=False)
eventid = Column(String(100), index=True, nullable=False)
name = Column(String(200))
content = Column(String)
class UserDomain(Base):
__tablename__ = "userdomain"
userid = Column(Integer,ForeignKey("user.id"))
name = Column(String(100), nullable=False)
url = Column(String(200), nullable=False)
kintoneuser = Column(String(100), nullable=False)
kintonepwd = Column(String(100), nullable=False)
active = Column(Boolean, default=False)

View File

@@ -1,7 +1,12 @@
from pydantic import BaseModel from pydantic import BaseModel
from datetime import datetime
import typing as t import typing as t
class Base(BaseModel):
create_time: datetime
update_time: datetime
class UserBase(BaseModel): class UserBase(BaseModel):
email: str email: str
is_active: bool = True is_active: bool = True
@@ -69,3 +74,53 @@ class Kintone(BaseModel):
class Config: class Config:
orm_mode = True orm_mode = True
class Action(BaseModel):
id: int
name: str = None
title: str = None
subtitle: str = None
outputpoints: str = None
property: str = None
class Config:
orm_mode = True
class FlowBase(BaseModel):
flowid: str
appid: str
eventid: str
name: str = None
content: str = None
class Flow(Base):
id: int
flowid: str
appid: str
eventid: str
name: str = None
content: str = None
class Config:
orm_mode = True
class DomainBase(BaseModel):
id: int
userid: int
name: str
url: str
kintoneuser: str
kintonepwd: str
active:bool = False
class Domain(Base):
id: int
userid: str
name: str
url: str
kintoneuser: str
kintonepwd: str
active:bool
class Config:
orm_mode = True

View File

@@ -21,3 +21,4 @@ pandas==2.0.3
openpyxl==3.1.2 openpyxl==3.1.2
deepdiff==6.3.1 deepdiff==6.3.1
pymssql==2.2.7 pymssql==2.2.7
psycopg2==2.9.8

View File

@@ -10,6 +10,7 @@
"dependencies": { "dependencies": {
"@quasar/extras": "^1.16.4", "@quasar/extras": "^1.16.4",
"axios": "^1.4.0", "axios": "^1.4.0",
"pinia": "^2.1.6",
"quasar": "^2.6.0", "quasar": "^2.6.0",
"uuid": "^9.0.0", "uuid": "^9.0.0",
"vue": "^3.0.0", "vue": "^3.0.0",
@@ -4079,6 +4080,56 @@
"url": "https://github.com/sponsors/jonschlinkert" "url": "https://github.com/sponsors/jonschlinkert"
} }
}, },
"node_modules/pinia": {
"version": "2.1.6",
"resolved": "https://registry.npmjs.org/pinia/-/pinia-2.1.6.tgz",
"integrity": "sha512-bIU6QuE5qZviMmct5XwCesXelb5VavdOWKWaB17ggk++NUwQWWbP5YnsONTk3b752QkW9sACiR81rorpeOMSvQ==",
"dependencies": {
"@vue/devtools-api": "^6.5.0",
"vue-demi": ">=0.14.5"
},
"funding": {
"url": "https://github.com/sponsors/posva"
},
"peerDependencies": {
"@vue/composition-api": "^1.4.0",
"typescript": ">=4.4.4",
"vue": "^2.6.14 || ^3.3.0"
},
"peerDependenciesMeta": {
"@vue/composition-api": {
"optional": true
},
"typescript": {
"optional": true
}
}
},
"node_modules/pinia/node_modules/vue-demi": {
"version": "0.14.6",
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.6.tgz",
"integrity": "sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==",
"hasInstallScript": true,
"bin": {
"vue-demi-fix": "bin/vue-demi-fix.js",
"vue-demi-switch": "bin/vue-demi-switch.js"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/antfu"
},
"peerDependencies": {
"@vue/composition-api": "^1.0.0-rc.1",
"vue": "^3.0.0-0 || ^2.6.0"
},
"peerDependenciesMeta": {
"@vue/composition-api": {
"optional": true
}
}
},
"node_modules/postcss": { "node_modules/postcss": {
"version": "8.4.25", "version": "8.4.25",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.25.tgz", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.25.tgz",
@@ -4955,7 +5006,7 @@
"version": "4.9.5", "version": "4.9.5",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
"integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
"dev": true, "devOptional": true,
"bin": { "bin": {
"tsc": "bin/tsc", "tsc": "bin/tsc",
"tsserver": "bin/tsserver" "tsserver": "bin/tsserver"

View File

@@ -15,6 +15,7 @@
"dependencies": { "dependencies": {
"@quasar/extras": "^1.16.4", "@quasar/extras": "^1.16.4",
"axios": "^1.4.0", "axios": "^1.4.0",
"pinia": "^2.1.6",
"quasar": "^2.6.0", "quasar": "^2.6.0",
"uuid": "^9.0.0", "uuid": "^9.0.0",
"vue": "^3.0.0", "vue": "^3.0.0",

View File

@@ -1,6 +1,12 @@
<template> <template>
<div class="q-pa-md"> <div class="q-pa-md">
<q-table :title="name+'一覧'" row-key="name" :selection="type" v-model:selected="selected" :columns="columns" :rows="rows" /> <q-table row-key="name" :selection="type" v-model:selected="selected" :columns="columns" :rows="rows"
class="action-table"
flat bordered
virtual-scroll
:pagination="pagination"
:rows-per-page-options="[0]"
/>
</div> </div>
</template> </template>
<script> <script>
@@ -17,11 +23,11 @@ export default {
const columns = [ const columns = [
{ name: 'name', required: true,label: 'アクション名',align: 'left',field: 'name',sortable: true}, { name: 'name', required: true,label: 'アクション名',align: 'left',field: 'name',sortable: true},
{ name: 'desc', align: 'left', label: '説明', field: 'desc', sortable: true }, { name: 'desc', align: 'left', label: '説明', field: 'desc', sortable: true },
{ name: 'content', label: '内容', field: 'content', sortable: true } // { name: 'content', label: '内容', field: 'content', sortable: true }
] ]
const rows = reactive([]) const rows = reactive([])
onMounted(async () => { onMounted(async () => {
await api.get('http://127.0.0.1:8000/api/kintone/2').then(res =>{ await api.get('http://127.0.0.1:8000/api/kintone/1').then(res =>{
res.data.forEach((item) => res.data.forEach((item) =>
{ {
rows.push({name:item.name,desc:item.desc,content:item.content}); rows.push({name:item.name,desc:item.desc,content:item.content});
@@ -33,8 +39,16 @@ export default {
columns, columns,
rows, rows,
selected: ref([]), selected: ref([]),
pagination:ref({
rowsPerPage:0
})
} }
}, },
} }
</script> </script>
<style lang="scss">
.action-table{
height: 100%;
}
</style>

View File

@@ -8,7 +8,7 @@ import { ref,onMounted,reactive } from 'vue'
import { api } from 'boot/axios'; import { api } from 'boot/axios';
export default { export default {
name: 'appSelect', name: 'AppSelect',
props: { props: {
name: String, name: String,
type: String type: String

View File

@@ -20,7 +20,7 @@
<script> <script>
export default { export default {
name: 'showDialog', name: 'ShowDialog',
props: { props: {
name:String, name:String,
visible: Boolean, visible: Boolean,

View File

@@ -1,73 +0,0 @@
<template>
<div class="q-py-md">
<q-list>
<q-expansion-item
group="somegroup"
label="レコードを追加画面"
default-opened
>
<q-card-section>
<q-checkbox v-model="setting.v1" label="追加画面表示した時" />
<q-checkbox v-model="setting.v2" label="保存をクリックした時" />
<q-checkbox v-model="setting.v3" label="保存成功した時" />
</q-card-section>
</q-expansion-item>
<q-expansion-item group="somegroup" label="レコード編集画面">
<q-card>
<q-card-section>
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Quidem,
eius reprehenderit eos corrupti commodi magni quaerat ex numquam,
dolorum officiis modi facere maiores architecto suscipit iste
eveniet doloribus ullam aliquid.
</q-card-section>
</q-card>
</q-expansion-item>
<q-expansion-item group="somegroup" label="レコード詳細画面">
<q-card>
<q-card-section>
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Quidem,
eius reprehenderit eos corrupti commodi magni quaerat ex numquam,
dolorum officiis modi facere maiores architecto suscipit iste
eveniet doloribus ullam aliquid.
</q-card-section>
</q-card>
</q-expansion-item>
<q-expansion-item group="somegroup" label="レコード一覧画面">
<q-card class="bg-teal-2">
<q-card-section>
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Quidem,
eius reprehenderit eos corrupti commodi magni quaerat ex numquam,
dolorum officiis modi facere maiores architecto suscipit iste
eveniet doloribus ullam aliquid.
</q-card-section>
</q-card>
</q-expansion-item>
</q-list>
</div>
<q-btn @click="clear" label="clear"/>
</template>
<script setup lang="ts">
import { ref, Ref } from 'vue';
interface Setting {
v1: boolean;
v2: boolean;
v3: boolean;
}
const setting: Ref<Setting> = ref({
v1: true,
v2: true,
v3: false,
});
let clear = () => {
setting.value.v1 = false
setting.value.v2 = false
setting.value.v3 = false
}
</script>

View File

@@ -1,12 +1,13 @@
<template> <template>
<div class="q-py-md"> <div class="q-py-md">
<q-tree :nodes="LeftDataBus.root" node-key="label"> <q-tree
<template #header-rg="p"> no-connectors
<ControlPanelTreeRadio selected-color="primary"
:node="p.node" default-expand-all
:dataBus="LeftDataBus" :nodes="LeftDataBus.root"
></ControlPanelTreeRadio> v-model:selected="flowNames1"
</template> node-key="label"
>
</q-tree> </q-tree>
</div> </div>
</template> </template>
@@ -16,9 +17,13 @@ import {
LeftDataBus, LeftDataBus,
setControlPanelE, setControlPanelE,
} from 'components/flowEditor/left/DataBus'; } from 'components/flowEditor/left/DataBus';
import ControlPanelTreeRadio from './ControlPanelTreeRadio.vue'; import { ref } from 'vue';
import { storeToRefs } from 'pinia';
import { useFlowEditorStore } from 'stores/flowEditor';
// 应该在page中用网络请求获取值并初始化组件 // 应该在page中用网络请求获取值并初始化组件
// 然后在page中执行setControlPane设置databus // 然后在page中执行setControlPane设置databus
const store = useFlowEditorStore();
const { flowNames1 } = storeToRefs(store);
setControlPanelE(); setControlPanelE();
</script> </script>

View File

@@ -1,23 +0,0 @@
<template>
<q-radio v-model="model" :val="node.value" :label="node.label" />
</template>
<script setup lang="ts">
import { computed } from 'vue';
import { LeftData, ControlPanelData } from 'components/flowEditor/left/DataBus';
const props = defineProps(['node', 'dataBus']);
const node = computed(() => props.node as ControlPanelData);
const model = computed({
get() {
return (props.dataBus as LeftData).data?.get(node.value.group ?? 'n');
},
set(newValue) {
(props.dataBus as LeftData).data?.set(
node.value.group ?? 'n',
newValue ?? ''
);
},
});
</script>

View File

@@ -1,36 +1,42 @@
<template> <template>
<div class="ItemSelector q-pa-sm"> <div
<div class="row"> class="row"
<div class="col-auto"> style="
<q-icon name="widgets" color="primary" size="2.5em" /> border-radius: 2px;
</div> box-shadow: rgba(255, 255, 255, 0.1) 0px 0px 0px 1px inset,
<div class="col flex"> rgba(0, 0, 0, 0.3) 0px 0px 0px 1px;
<div class="q-pa-sm flex" style="align-items: center">{{title}}</div> "
</div> >
<div class="col-auto flex"> <q-icon
<div class="flex" style="align-items: center"> class="self-center q-ma-sm"
<q-btn name="widgets"
class="q-px-sm" color="grey-9"
color="white" style="font-size: 2em"
size="sm"
text-color="black"
label="変 更"
dense
/> />
<div class="col-7 self-center ellipsis">
{{ actName }}
</div> </div>
</div>
<div class="self-center">
<q-btn
outline
dense
label="変 更"
padding="none sm"
color="primary"
></q-btn>
</div> </div>
</div> </div>
</template> </template>
<script setup> <script>
import { ref } from 'vue'; import { computed } from 'vue';
const title = ref('勤怠管理') export default {
props: ['actName'],
setup(props) {
const actName = computed(() => props.actName);
},
};
</script> </script>
<style lang="sass">
.ItemSelector
border: 0.15em solid rgba(#999, .4)
border-radius: 0.4em
</style>

View File

@@ -0,0 +1,85 @@
<template>
<div
class="row"
style="
border-radius: 2px;
box-shadow: rgba(255, 255, 255, 0.1) 0px 0px 0px 1px inset,
rgba(0, 0, 0, 0.3) 0px 0px 0px 1px;
">
<q-icon
class="self-center q-ma-sm"
name="widgets"
color="grey-9"
style="font-size: 2em"
/>
<div class="col-7 self-center ellipsis">
{{ selectedApp.name }}
</div>
<div class="self-center">
<q-btn
outline
dense
label="変 更"
padding="none sm"
color="primary"
@click="showAppDialog"
></q-btn>
</div>
</div>
<ShowDialog v-model:visible="showSelectApp" name="アプリ" @close="closeDg">
<AppSelect ref="appDg" name="アプリ" type="single"></AppSelect>
</ShowDialog>
</template>
<script lang="ts">
import { defineComponent,ref } from 'vue';
import {AppInfo} from '../../types/ActionTypes'
import ShowDialog from '../../components/ShowDialog.vue';
import AppSelect from '../../components/AppSelect.vue';
import { useFlowEditorStore } from 'stores/flowEditor';
export default defineComponent({
name: 'AppSelector',
emits:[
"appSelected"
],
components:{
AppSelect,
ShowDialog
},
setup(props, context) {
const store = useFlowEditorStore();
const appDg = ref();
const showSelectApp=ref(false);
const selectedApp =ref<AppInfo>({
appId:"",
name:"",
});
const closeDg=(val :any)=>{
showSelectApp.value=false;
console.log("Dialog closed->",val);
if (val == 'OK') {
const data = appDg.value.selected[0];
console.log(data);
selectedApp.value={
appId:data.id ,
name:data.name
};
store.setApp(selectedApp.value);
store.setFlow();
}
}
const showAppDialog=()=>{
showSelectApp.value=true;
}
return {
store,
selectedApp,
showSelectApp,
showAppDialog,
closeDg,
appDg
}
}
});
</script>

View File

@@ -0,0 +1,72 @@
<template>
<div class="q-pa-md q-gutter-sm">
<q-tree
:nodes="eventTree.screens"
node-key="label"
children-key="events"
no-connectors
v-model:expanded="expanded"
:dense="true"
>
<template v-slot:default-header="prop">
<div class="row col items-start no-wrap event-node" @click="onSelected(prop.node)">
<q-icon v-if="prop.node.eventId"
name="play_circle"
:color="prop.node.hasFlow?'green':'grey'"
size="16px" class="q-mr-sm">
</q-icon>
<div class="no-wrap" :class="selectedEvent && prop.node.eventId===selectedEvent.eventId?'selected-node':''">{{ prop.node.label }}</div>
</div>
</template>
</q-tree>
</div>
</template>
<script lang="ts">
import { defineComponent, computed, ref } from 'vue';
import { kintoneEvents,KintoneEvent } from '../../types/KintoneEvents';
import { storeToRefs } from 'pinia';
import { useFlowEditorStore } from 'stores/flowEditor';
export default defineComponent({
name: 'EventTree',
setup(props, context) {
const store = useFlowEditorStore();
const eventTree=ref(kintoneEvents);
const selectedFlow = store.currentFlow;
const expanded=ref([
selectedFlow?.getRoot()?.title
]);
const selectedEvent = ref<KintoneEvent|null>(null);
const onSelected=(node:KintoneEvent)=>{
if(!node.eventId){
return;
}
selectedEvent.value=node;
}
return {
eventTree,
expanded,
onSelected,
selectedEvent,
store
}
}
});
</script>
<style lang="scss">
.nowrap{
flex-wrap:nowarp;
text-wrap:nowarp;
}
.event-node{
cursor:pointer;
}
.selected-node{
color: $primary;
font-weight: bolder;
}
.event-node:hover{
background-color: $light-blue-1;
}
</style>

View File

@@ -5,7 +5,7 @@
</template> </template>
</q-input> </q-input>
<show-dialog v-model:visible="show" name="フィールド一覧" @close="closeDg"> <show-dialog v-model:visible="show" name="フィールド一覧" @close="closeDg">
<field-select ref="appDg" name="フィールド" type="single" :appId="1"></field-select> <field-select ref="appDg" name="フィールド" type="single" :appId="store.appInfo?.appId"></field-select>
</show-dialog> </show-dialog>
</template> </template>
@@ -13,7 +13,7 @@
import { defineComponent, ref ,watchEffect} from 'vue'; import { defineComponent, ref ,watchEffect} from 'vue';
import ShowDialog from '../ShowDialog.vue'; import ShowDialog from '../ShowDialog.vue';
import FieldSelect from '../FieldSelect.vue'; import FieldSelect from '../FieldSelect.vue';
import { useFlowEditorStore } from 'stores/flowEditor';
export default defineComponent({ export default defineComponent({
name: 'FieldInput', name: 'FieldInput',
components: { components: {
@@ -35,6 +35,7 @@ export default defineComponent({
const appDg = ref(); const appDg = ref();
const show = ref(false); const show = ref(false);
const selectedField = ref(props.modelValue); const selectedField = ref(props.modelValue);
const store = useFlowEditorStore();
const showDg = () => { const showDg = () => {
show.value = true; show.value = true;
@@ -51,6 +52,7 @@ export default defineComponent({
}); });
return { return {
store,
appDg, appDg,
show, show,
showDg, showDg,

View File

@@ -0,0 +1,21 @@
import { api } from 'boot/axios';
export class Auth
{
async login(user:string,pwd:string):Promise<boolean>
{
const params = new URLSearchParams();
params.append('username', user);
params.append('password', pwd);
try{
const result = await api.post(`http://127.0.0.1:8000/api/token`,params);
console.info(result);
localStorage.setItem('Token', result.data.access_token);
return true;
}catch(e)
{
console.info(e);
return false;
}
}
}

View File

@@ -0,0 +1,37 @@
import { api } from 'boot/axios';
import { ActionFlow } from 'src/types/ActionTypes';
export class FlowCtrl
{
async getFlows(appId:string):Promise<ActionFlow[]>
{
const result = await api.get(`http://127.0.0.1:8000/api/flows/${appId}`);
//console.info(result.data);
if(!result.data || !Array.isArray(result.data)){
return [];
}
const flows:ActionFlow[]=[];
for(const flow of result.data){
flows.push(ActionFlow.fromJSON(flow.content));
}
return flows;
}
async SaveFlow(jsonData:any):Promise<boolean>
{
const result = await api.post('http://127.0.0.1:8000/api/flow',jsonData);
console.info(result.data)
return true;
}
async UpdateFlow(jsonData:any):Promise<boolean>
{
const result = await api.put('http://127.0.0.1:8000/api/flow/' + jsonData.flowid,jsonData);
console.info(result.data)
return true;
}
}

View File

@@ -14,7 +14,9 @@
Kintone App Builder Kintone App Builder
<q-badge align="top" outline>V{{ env.version }}</q-badge> <q-badge align="top" outline>V{{ env.version }}</q-badge>
</q-toolbar-title> </q-toolbar-title>
<q-btn flat round dense icon="logout" @click="authStore.logout()"/>
</q-toolbar> </q-toolbar>
</q-header> </q-header>
<q-drawer <q-drawer
@@ -46,6 +48,9 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue'; import { ref } from 'vue';
import EssentialLink, { EssentialLinkProps } from 'components/EssentialLink.vue'; import EssentialLink, { EssentialLinkProps } from 'components/EssentialLink.vue';
import { useAuthStore } from 'stores/useAuthStore';
const authStore = useAuthStore();
const essentialLinks: EssentialLinkProps[] = [ const essentialLinks: EssentialLinkProps[] = [
{ {
@@ -59,7 +64,7 @@ const essentialLinks: EssentialLinkProps[] = [
title: 'フローエディター', title: 'フローエディター',
caption: 'flowChart', caption: 'flowChart',
icon: 'account_tree', icon: 'account_tree',
link: '/#/flowChart', link: '/#/flowEditor2',
target:'_self' target:'_self'
}, },
{ {

View File

@@ -0,0 +1,160 @@
<template>
<div class="q-pa-md q-gutter-sm event-tree">
<q-drawer
side="left"
overlay
bordered
v-model="drawerLeft"
:show-if-above="false"
elevated
>
<!-- <q-card class="column full-height" style="width: 300px">
<q-card-section> -->
<div class="flex-center fixd-top" >
<AppSelector />
</div>
<!-- </q-card-section> -->
<q-separator />
<!-- <q-card-section> -->
<div class="flex-center">
<EventTree />
</div>
<!-- </q-card-section> -->
<!-- </q-card> -->
</q-drawer>
</div>
<q-page>
<div class="q-pa-md q-gutter-sm">
<div class="flowchart" v-if="store.currentFlow">
<node-item v-for="(node,) in store.currentFlow.actionNodes" :key="node.id"
:isSelected="node===state.activeNode" :actionNode="node"
@addNode="addNode"
@nodeSelected="onNodeSelected"
@nodeEdit="onNodeEdit"
@deleteNode="onDeleteNode"
@deleteAllNextNodes="onDeleteAllNextNodes"
></node-item>
</div>
</div>
</q-page>
<PropertyPanel :actionNode="state.activeNode" v-model:drawerRight="drawerRight"></PropertyPanel>
<ShowDialog v-model:visible="showAddAction" name="アクション" @close="closeDg">
<action-select ref="appDg" name="model" type="single"></action-select>
</ShowDialog>
</template>
<script setup lang="ts">
import {ref,reactive,computed,onMounted} from 'vue';
import {IActionNode, ActionNode, IActionFlow, ActionFlow,RootAction, IActionProperty } from 'src/types/ActionTypes';
import { storeToRefs } from 'pinia';
import { useFlowEditorStore } from 'stores/flowEditor';
import NodeItem from 'src/components/main/NodeItem.vue';
import ShowDialog from 'components/ShowDialog.vue';
import ActionSelect from 'components/ActionSelect.vue';
import PropertyPanel from 'components/right/PropertyPanel.vue';
import AppSelector from 'components/left/AppSelector.vue';
import EventTree from 'components/left/EventTree.vue';
import {FlowCtrl } from '../control/flowctrl';
const drawerLeft = ref(true);
const store = useFlowEditorStore();
// ref関数を使ってtemplateとバインド
const state=reactive({
activeNode:{
id:""
},
})
const appDg = ref();
const prevNodeIfo=ref({
prevNode:{} as IActionNode,
inputPoint:""
});
const refFlow = ref<ActionFlow|null>(null);
const showAddAction=ref(false);
const drawerRight=ref(false);
const model=ref("");
const addActionNode=(action:IActionNode)=>{
// refFlow.value?.actionNodes.push(action);
store.currentFlow?.actionNodes.push(action);
}
const addNode=(node:IActionNode,inputPoint:string)=>{
showAddAction.value=true;
prevNodeIfo.value.prevNode=node;
prevNodeIfo.value.inputPoint=inputPoint;
}
const onNodeSelected=(node:IActionNode)=>{
//右パネルが開いている場合、自動閉じる
if(drawerRight.value && state.activeNode.id!==node.id){
drawerRight.value=false;
}
state.activeNode = node;
}
const onNodeEdit=(node:IActionNode)=>{
state.activeNode = node;
drawerRight.value=true;
}
const onDeleteNode=(node:IActionNode)=>{
if(!store.currentFlow) return;
store.currentFlow?.removeNode(node);
}
const onDeleteAllNextNodes=(node:IActionNode)=>{
if(!store.currentFlow) return;
store.currentFlow?.removeAllNext(node.id);
}
const closeDg=(val :any)=>{
console.log("Dialog closed->",val);
if (val == 'OK') {
const data = appDg.value.selected[0];
const actionProps=JSON.parse(data.content);
const action = new ActionNode(data.name,data.desc,"",[],actionProps);
store.currentFlow?.addNode(action, prevNodeIfo.value.prevNode,prevNodeIfo.value.inputPoint);
}
}
const fetchData= async ()=>{
const flowCtrl = new FlowCtrl();
if(store.appInfo===undefined) return;
const actionFlows = await flowCtrl.getFlows(store.appInfo?.appId);
if(actionFlows && actionFlows.length>0){
store.setFlows(actionFlows);
}
if(actionFlows && actionFlows.length==1){
store.selectFlow(actionFlows[0]);
}
refFlow.value=actionFlows[0];
const root =refFlow.value.getRoot();
if(root){
state.activeNode=root;
}
}
onMounted(() => {
fetchData();
});
</script>
<style lang="scss">
.flowchart{
padding-top: 10px;
}
.flow-toolbar{
opacity: 50%;
}
.flow-container{
height: 91.5dvb;
overflow: hidden;
}
.event-tree .q-drawer {
top:50px;
z-index: 999;
}
</style>

View File

@@ -1,5 +1,6 @@
<template> <template>
<q-page> <q-page>
<div class="flowchart"> <div class="flowchart">
<node-item v-for="(node,) in refFlow.actionNodes" :key="node.id" <node-item v-for="(node,) in refFlow.actionNodes" :key="node.id"
:isSelected="node===state.activeNode" :actionNode="node" :isSelected="node===state.activeNode" :actionNode="node"
@@ -46,11 +47,10 @@ const saibanProps:IActionProperty[]=[{
} }
}]; }];
actionFlow.addNode(new ActionNode('自動採番','文書番号を自動採番する','',[],saibanProps)); actionFlow.addNode(new ActionNode('自動採番','文書番号を自動採番する','',[],saibanProps));
actionFlow.addNode(new ActionNode('入力データ取得','電話番号を取得する','')); actionFlow.addNode(new ActionNode('入力データ取得','電話番号を取得する',''));
const branchNode = actionFlow.addNode(new ActionNode('条件分岐','電話番号入力形式チャック','',['はい','いいえ'] )); const branchNode = actionFlow.addNode(new ActionNode('条件分岐','電話番号入力形式チャック','',['はい','いいえ'] ));
actionFlow.addNode(new ActionNode('入力データ取得','住所を取得する',''),branchNode,'はい'); // actionFlow.addNode(new ActionNode('入力データ取得','住所を取得する',''),branchNode,'はい');
actionFlow.addNode(new ActionNode('エラー表示','エラー表示して保存しない',''),branchNode,'いいえ' ); actionFlow.addNode(new ActionNode('エラー表示','エラー表示して保存しない',''),branchNode,'いいえ' );
// ref関数を使ってtemplateとバインド // ref関数を使ってtemplateとバインド

View File

@@ -1,37 +1,122 @@
<template> <template>
<q-page> <div>
<div class="q-pa-md"> <div class="q-ma-md">
<div class="q-gutter-sm row items-start"> <div class="q-gutter-xs row items-start">
<q-breadcrumbs> <q-breadcrumbs class="q-pt-xs q-mr-sm" active-color="black">
<q-breadcrumbs-el icon="home" to="/" /> <q-breadcrumbs-el icon="home" />
<q-breadcrumbs-el :label="title" icon="rule" /> <q-breadcrumbs-el :label="actName" />
</q-breadcrumbs> <q-breadcrumbs-el
</div> v-for="flowName in flowNames"
<div class="q-pa-md"> :key="flowName"
<div class="row"> :label="flowName"
<div class="col-2 column"> />
<ItemSelector />
<div class="col-auto"><ControlPanel /></div>
</div>
<!-- <div class="col">
</div> --> <q-breadcrumbs-el :label="flowNames1" />
</q-breadcrumbs>
<q-separator vertical class="q-mr-xs" />
<q-btn
unelevated
class="q-py-sm"
padding="none md none sm"
color="blue-1"
text-color="primary"
size="md"
@click="drawerLeft = !drawerLeft"
label="変 更"
icon="expand_more"
dense
/>
<q-space />
<q-btn
class="q-px-sm q-mr-sm"
color="white"
size="sm"
text-color="black"
label="キャンセル"
dense
/>
<q-btn
class="q-px-sm"
color="primary"
size="sm"
label="保存する"
dense
/>
</div> </div>
</div> </div>
<q-layout
container
style="height: 91.5dvb"
class="shadow-2 rounded-borders"
>
<q-drawer side="left" overlay bordered v-model="drawerLeft">
<div class="q-pa-sm fixed-right">
<q-btn
flat
round
color="primary"
icon="close"
@click="drawerLeft = !drawerLeft"
/>
</div>
<div class="q-mt-lg q-pa-sm">
<q-card-section>
<div class="flex-center">
<div class="row q-pl-md">
<p class="text-h6">アクション選択</p>
</div>
<ItemSelector :actName="actName" />
</div>
</q-card-section>
</div>
<q-separator />
<div class="q-mt-md q-pa-sm">
<q-card-section>
<p class="text-h6 q-pl-md q-mb-none">フロー選択</p>
<ControlPanel />
</q-card-section>
</div>
<q-separator />
<q-card-actions align="right">
<div class="q-pa-sm">
<q-btn
flat
color="primary"
size="md"
@click="drawerLeft = !drawerLeft"
label="ジャンプ"
dense
/>
</div>
</q-card-actions>
</q-drawer>
<FlowChartTest />
</q-layout>
</div> </div>
</q-page>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import ItemSelector from 'components/flowEditor/left/ItemSelector.vue'; import FlowChartTest from 'pages/FlowChartTest.vue';
import ControlPanel from 'components/flowEditor/left/ControlPanelC.vue'; import ControlPanel from 'components/flowEditor/left/ControlPanelC.vue';
interface FlowEditorPageProps { import ItemSelector from 'components/flowEditor/left/ItemSelector.vue';
title: string; import { ref } from 'vue';
} import { storeToRefs } from 'pinia';
import { useFlowEditorStore } from 'stores/flowEditor';
const actName = ref('勤怠管理 - 4');
const flowNames = ref(['レコードを追加画面', '保存をクリックした時']);
const drawerLeft = ref(false);
const store = useFlowEditorStore();
const { flowNames1 } = storeToRefs(store);
const props = withDefaults(defineProps<FlowEditorPageProps>(), {
title: 'FlowEditor',
});
</script> </script>
<style lang="sass"></style> <style lang="sass"></style>

View File

@@ -0,0 +1,117 @@
<template>
<div >
<div class="q-ma-md">
<div class="q-gutter-xs row items-start">
<q-btn
size="md"
@click="drawerLeft = !drawerLeft"
icon="keyboard_double_arrow_right"
round
/>
<q-space />
<q-btn
color="white"
size="sm"
text-color="black"
label="キャンセル"
dense
/>
<q-btn
class="q-px-sm"
color="primary"
size="sm"
label="保存する"
@click="save()"
dense
/>
</div>
</div>
<q-layout
container
class="flow-container shadow-2 rounded-borders"
>
<q-drawer side="left" overlay bordered v-model="drawerLeft">
<div class="q-pa-sm fixed-right">
<q-btn
flat
round
color="primary"
icon="close"
@click="drawerLeft = !drawerLeft"
/>
</div>
<div class="q-mt-lg q-pa-sm">
<q-card-section>
<div class="flex-center">
<ItemSelector />
</div>
</q-card-section>
</div>
<q-separator />
<div class="q-mt-md q-pa-sm">
<q-card-section>
<ControlPanel />
</q-card-section>
</div>
<q-separator />
<q-card-actions align="right">
<div class="q-pa-sm">
<q-btn
flat
color="primary"
size="md"
@click="drawerLeft = !drawerLeft"
label="ジャンプ"
dense
/>
</div>
</q-card-actions>
</q-drawer>
<FlowChartTest />
</q-layout>
</div>
</template>
<script setup lang="ts">
import FlowChartTest from 'pages/FlowChartTest.vue';
import ControlPanel from 'components/flowEditor/left/ControlPanelC.vue';
import ItemSelector from 'components/flowEditor/left/ItemSelector.vue';
import { ref } from 'vue';
import { storeToRefs } from 'pinia';
import { useFlowEditorStore } from 'stores/flowEditor';
import { FlowCtrl } from '../control/flowctrl'
const flowCtrl = new FlowCtrl();
const actName = ref('勤怠管理 - 4');
const drawerLeft = ref(false);
const store = useFlowEditorStore();
const { flowNames1 } = storeToRefs(store);
let isNew = ref(true);
const save = () =>{
if(isNew.value)
{
flowCtrl.SaveFlow({appid:'1',flowid:'flow123',eventid:'event123',name:'test',content:'[]'});
isNew.value = false;
}
else
{
flowCtrl.UpdateFlow({appid:'1',flowid:'flow123',eventid:'event123',name:'test',content:'[{"a":"b"}]'});
}
}
</script>
<style lang="scss">
.flow-toolbar{
opacity: 50%;
}
.flow-container{
height: calc(91.5dvb - 50px);
overflow: hidden;
}
</style>

View File

@@ -0,0 +1,100 @@
<template>
<q-layout view="lHh Lpr fff">
<q-page-container>
<q-page class="window-height window-width row justify-center items-center">
<div class="column q-pa-lg">
<div class="row">
<q-card square class="shadow-24" style="width:400px;height:540px;">
<q-card-section class="bg-primary">
<h4 class="text-h5 text-white q-my-md">{{ title}}</h4>
</q-card-section>
<q-card-section>
<q-form class="q-px-sm q-pt-xl" ref="loginForm">
<q-input square clearable v-model="email" type="email" lazy-rules
:rules="[required,isEmail,short]" label="メール">
<template v-slot:prepend>
<q-icon name="email" />
</template>
</q-input>
<q-input square clearable v-model="password" :type="passwordFieldType" lazy-rules
:rules="[required, short]" label="パスワード">
<template v-slot:prepend>
<q-icon name="lock" />
</template>
<template v-slot:append>
<q-icon :name="visibilityIcon" @click="switchVisibility" class="cursor-pointer" />
</template>
</q-input>
</q-form>
</q-card-section>
<q-card-actions class="q-px-lg">
<q-btn unelevated size="lg" color="secondary" @click="submit" class="full-width text-white"
:label="btnLabel" />
</q-card-actions>
</q-card>
</div>
</div>
</q-page>
</q-page-container>
</q-layout>>
</template>
<script setup lang="ts">
import { useQuasar } from 'quasar'
// import { useRouter } from 'vue-router';
import { ref } from 'vue';
// import { Auth } from '../control/auth'
import { useAuthStore } from 'stores/useAuthStore';
const authStore = useAuthStore();
const $q = useQuasar()
const loginForm = ref(null);
let title = ref('ログイン');
let email = ref('');
let password = ref('');
let visibility = ref(false);
let passwordFieldType = ref('password');
let visibilityIcon = ref('visibility');
let btnLabel = ref('ログイン');
const required = (val:string) => {
return (val && val.length > 0 || '必須項目')
}
const isEmail = (val:string) => {
const emailPattern = /^(?=[a-zA-Z0-9@._%+-]{6,254}$)[a-zA-Z0-9._%+-]{1,64}@(?:[a-zA-Z0-9-]{1,63}\.){1,8}[a-zA-Z]{2,63}$/
return (emailPattern.test(val) || '無効なメールアドレス')
}
const short = (val:string) => {
return (val && val.length > 3 || '値が短く過ぎる')
}
const switchVisibility = () => {
visibility.value = !visibility.value
passwordFieldType.value = visibility.value ? 'text' : 'password'
visibilityIcon.value = visibility.value ? 'visibility_off' : 'visibility'
}
const submit = () =>{
authStore.login(email.value,password.value).then((result)=>{
if(result)
{
$q.notify({
icon: 'done',
color: 'positive',
message: 'ログイン成功'
})
}
else
{
$q.notify({
icon: 'error',
color: 'negative',
message: 'ログイン失敗'
})
}
});
}
</script>

View File

@@ -0,0 +1,354 @@
<!-- <template>
<div class="q-pa-md" style="max-width: 400px">
<q-form
@submit="onSubmit"
@reset="onReset"
class="q-gutter-md"
>
<q-input
filled
v-model="name"
label="Your name *"
hint="Kintone envirment name"
lazy-rules
:rules="[ val => val && val.length > 0 || 'Please type something']"
/>
<q-input
filled type="url"
v-model="url"
label="Kintone url"
hint="Kintone domain address"
lazy-rules
:rules="[ val => val && val.length > 0,isDomain || 'Please type something']"
/>
<q-input
filled
v-model="username"
label="Login user "
hint="Kintone user name"
lazy-rules
:rules="[ val => val && val.length > 0 || 'Please type something']"
/>
<q-input v-model="password" filled :type="isPwd ? 'password' : 'text'" hint="Password with toggle" label="User password">
<template v-slot:append>
<q-icon
:name="isPwd ? 'visibility_off' : 'visibility'"
class="cursor-pointer"
@click="isPwd = !isPwd"
/>
</template>
</q-input>
<q-toggle v-model="accept" label="Active Domain" />
<div>
<q-btn label="Submit" type="submit" color="primary"/>
<q-btn label="Reset" type="reset" color="primary" flat class="q-ml-sm" />
</div>
</q-form>
</div>
</template>
<script>
import { useQuasar } from 'quasar'
import { ref } from 'vue'
export default {
setup () {
const $q = useQuasar()
const name = ref(null)
const age = ref(null)
const accept = ref(false)
const isPwd =ref(true)
return {
name,
age,
accept,
isPwd,
isDomain(val) {
const domainPattern = /^https?\/\/:([a-zA-Z] +\.){1}([a-zA-Z]+)\.([a-zA-Z]+)$/;
return (domainPattern.test(val) || '無効なURL')
},
onSubmit () {
if (accept.value !== true) {
$q.notify({
color: 'red-5',
textColor: 'white',
icon: 'warning',
message: 'You need to accept the license and terms first'
})
}
else {
$q.notify({
color: 'green-4',
textColor: 'white',
icon: 'cloud_done',
message: 'Submitted'
})
}
},
onReset () {
name.value = null
age.value = null
accept.value = false
}
}
}
}
</script> -->
<template>
<div class="q-pa-md">
<q-table grid grid-header title="Domain" selection="single" :rows="rows" :columns="columns" v-model:selected="selected" row-key="name" :filter="filter" hide-header>
<template v-slot:top>
<div class="q-pa-md q-gutter-sm">
<q-btn color="primary" size="sm" label=" 新規 " @click="newDomain()" dense />
</div>
<q-space />
<q-input borderless dense debounce="300" v-model="filter" placeholder="Search">
<template v-slot:append>
<q-icon name="search" />
</template>
</q-input>
</template>
<template v-slot:item="props">
<div class="q-pa-xs col-xs-12 col-sm-6 col-md-4">
<q-card>
<q-card-section>
<div class="q-table__grid-item-row">
<div class="q-table__grid-item-title">Name</div>
<div class="q-table__grid-item-value">{{ props.row.name }}</div>
</div>
<div class="q-table__grid-item-row">
<div class="q-table__grid-item-title">Domain</div>
<div class="q-table__grid-item-value">{{ props.row.url }}</div>
</div>
<div class="q-table__grid-item-row">
<div class="q-table__grid-item-title">Account</div>
<div class="q-table__grid-item-value">{{ props.row.kintoneuser }}</div>
</div>
</q-card-section>
<q-separator />
<q-card-actions align="right">
<q-btn flat @click = "editDomain(props.row)">編集</q-btn>
<q-btn flat @click = "deleteConfirm(props.row)">削除</q-btn>
</q-card-actions>
</q-card>
</div>
</template>
</q-table>
<q-dialog :model-value="show" persistent>
<q-card style="min-width: 400px">
<q-card-section>
<div class="text-h6">Kintone Account</div>
</q-card-section>
<q-card-section class="q-pt-none">
<q-form class="q-gutter-md">
<q-input filled v-model="name" label="Your name *" hint="Kintone envirment name" lazy-rules
:rules="[val => val && val.length > 0 || 'Please type something']" />
<q-input filled type="url" v-model="url" label="Kintone url" hint="Kintone domain address" lazy-rules
:rules="[val => val && val.length > 0, isDomain || 'Please type something']" />
<q-input filled v-model="kintoneuser" label="Login user " hint="Kintone user name" lazy-rules
:rules="[val => val && val.length > 0 || 'Please type something']" />
<q-input v-model="kintonepwd" filled :type="isPwd ? 'password' : 'text'" hint="Password with toggle"
label="User password">
<template v-slot:append>
<q-icon :name="isPwd ? 'visibility_off' : 'visibility'" class="cursor-pointer" @click="isPwd = !isPwd" />
</template>
</q-input>
<q-toggle v-model="active" label="Active Domain" />
</q-form>
</q-card-section>
<q-card-actions align="right" class="text-primary">
<q-btn label="Save" type="submit" color="primary" @click="onSubmit"/>
<q-btn label="Cancel" type="cancel" color="primary" flat class="q-ml-sm" @click="closeDg()"/>
</q-card-actions>
</q-card>
</q-dialog>
<q-dialog v-model="confirm" persistent>
<q-card>
<q-card-section class="row items-center">
<q-avatar icon="confirm" color="primary" text-color="white" />
<span class="q-ml-sm">削除してもよろしいですか</span>
</q-card-section>
<q-card-actions align="right">
<q-btn flat label="Cancel" color="primary" v-close-popup />
<q-btn flat label="OK" color="primary" v-close-popup @click = "deleteDomain()"/>
</q-card-actions>
</q-card>
</q-dialog>
</div>
</template>
<script setup lang="ts">
import { useQuasar } from 'quasar'
import { ref, onMounted, reactive } from 'vue'
import { api } from 'boot/axios';
const $q = useQuasar()
const selected = ref([])
const name = ref('')
const active = ref(false)
const isPwd =ref(true)
const url =ref('')
const kintoneuser =ref('')
const kintonepwd =ref('')
const show = ref(false);
const confirm = ref(false)
let editId = ref(0);
const columns = [
{ name: 'id'},
{
name: 'name',
required: true,
label: 'Name',
align: 'left',
field: row => row.name,
sortable: true
},
{ name: 'url', align: 'center', label: 'Domain', field: 'url', sortable: true },
{ name: 'kintoneuser', label: 'User', field: 'kintoneuser', sortable: true },
{ name: 'kintonepwd' }
]
const rows = reactive([])
const newDomain = () => {
editId.value = 0;
show.value = true;
};
const editDomain = (row:object) => {
editId.value = row.id;
name.value = row.name;
url.value = row.url;
kintoneuser.value = row.kintoneuser;
kintonepwd.value = row.kintonepwd;
isPwd.value = true;
active.value = false;
show.value = true;
};
const deleteConfirm = (row:object) => {
confirm.value = true;
editId.value = row.id;
};
const deleteDomain = () => {
api.delete(`http://127.0.0.1:8000/api/domain/1/`+ editId.value).then(() =>{
getDomain();
})
editId.value = 0;
};
const closeDg = () => {
show.value = false;
onReset();
};
const getDomain = () => {
api.get(`http://127.0.0.1:8000/api/domain/1`).then(res => {
rows.length = 0;
res.data.forEach((item) => {
rows.push({ id:item.id,name: item.name, url: item.url, kintoneuser: item.kintoneuser, kintonepwd: item.kintonepwd });
}
)
});
}
onMounted(() => {
getDomain();
})
const isDomain = (val) =>{
// const domainPattern = /^https\/\/:([a-zA-Z] +\.){1}([a-zA-Z]+)\.([a-zA-Z]+)$/;
// return (domainPattern.test(val) || '無効なURL')
return true;
};
const onSubmit = () =>{
if(editId.value !== 0)
{
api.put(`http://127.0.0.1:8000/api/domain`,{
'id': editId.value,
'userid': 1,
'name': name.value,
'url': url.value,
'kintoneuser': kintoneuser.value,
'kintonepwd': kintonepwd.value,
'active': active.value
}).then(() =>{
getDomain();
closeDg();
onReset();
})
}
else
{
api.post(`http://127.0.0.1:8000/api/domain`,{
'id': 0,
'userid': 1,
'name': name.value,
'url': url.value,
'kintoneuser': kintoneuser.value,
'kintonepwd': kintonepwd.value,
'active': active.value
}).then(() =>{
getDomain();
closeDg();
onReset();
})
}
// if (accept.value !== true) {
// $q.notify({
// color: 'red-5',
// textColor: 'white',
// icon: 'warning',
// message: 'You need to accept the license and terms first'
// })
// }
// else {
// $q.notify({
// color: 'green-4',
// textColor: 'white',
// icon: 'cloud_done',
// message: 'Submitted'
// })
// }
};
const onReset = () => {
name.value = '';
url.value = '';
kintoneuser.value = '';
kintonepwd.value ='';
isPwd.value = true;
active.value = false;
editId.value = 0;
}
</script>

View File

@@ -7,7 +7,7 @@ import {
} from 'vue-router'; } from 'vue-router';
import routes from './routes'; import routes from './routes';
import { useAuthStore } from 'stores/useAuthStore';
/* /*
* If not building with SSR mode, you can * If not building with SSR mode, you can
* directly export the Router instantiation; * directly export the Router instantiation;
@@ -17,12 +17,31 @@ import routes from './routes';
* with the Router instance. * with the Router instance.
*/ */
export default route(function (/* { store, ssrContext } */) { // export default route(function (/* { store, ssrContext } */) {
const createHistory = process.env.SERVER // const createHistory = process.env.SERVER
// ? createMemoryHistory
// : (process.env.VUE_ROUTER_MODE === 'history' ? createWebHistory : createWebHashHistory);
// const Router = createRouter({
// scrollBehavior: () => ({ left: 0, top: 0 }),
// routes,
// // Leave this as is and make changes in quasar.conf.js instead!
// // quasar.conf.js -> build -> vueRouterMode
// // quasar.conf.js -> build -> publicPath
// history: createHistory(process.env.VUE_ROUTER_BASE),
// });
// return Router;
// });
const createHistory = process.env.SERVER
? createMemoryHistory ? createMemoryHistory
: (process.env.VUE_ROUTER_MODE === 'history' ? createWebHistory : createWebHashHistory); : (process.env.VUE_ROUTER_MODE === 'history' ? createWebHistory : createWebHashHistory);
const Router = createRouter({ export const Router = createRouter({
scrollBehavior: () => ({ left: 0, top: 0 }), scrollBehavior: () => ({ left: 0, top: 0 }),
routes, routes,
@@ -32,5 +51,23 @@ export default route(function (/* { store, ssrContext } */) {
history: createHistory(process.env.VUE_ROUTER_BASE), history: createHistory(process.env.VUE_ROUTER_BASE),
}); });
export default route(function (/* { store, ssrContext } */) {
return Router; return Router;
});
Router.beforeEach(async (to) => {
// clear alert on route change
//const alertStore = useAlertStore();
//alertStore.clear();
// redirect to login page if not logged in and trying to access a restricted page
const publicPages = ['/login'];
const authRequired = !publicPages.includes(to.path);
const authStore = useAuthStore();
if (authRequired && !authStore.token) {
authStore.returnUrl = to.fullPath;
return '/login';
}
}); });

View File

@@ -1,6 +1,11 @@
import { RouteRecordRaw } from 'vue-router'; import { RouteRecordRaw } from 'vue-router';
const routes: RouteRecordRaw[] = [ const routes: RouteRecordRaw[] = [
{
path: '/login',
component: () => import('pages/LoginPage.vue')
},
{ {
path: '/', path: '/',
component: () => import('layouts/MainLayout.vue'), component: () => import('layouts/MainLayout.vue'),
@@ -11,7 +16,10 @@ const routes: RouteRecordRaw[] = [
{ path: 'flow', component: () => import('pages/testFlow.vue') }, { path: 'flow', component: () => import('pages/testFlow.vue') },
{ path: 'flowchart', component: () => import('pages/FlowChartTest.vue') }, { path: 'flowchart', component: () => import('pages/FlowChartTest.vue') },
{ path: 'flowEditor', component: () => import('pages/FlowEditorPage.vue') }, { path: 'flowEditor', component: () => import('pages/FlowEditorPage.vue') },
{ path: 'flowEditor2', component: () => import('pages/FlowChart.vue') },
{ path: 'flowChart2', component: () => import('pages/FlowEditorPage2.vue') },
{ path: 'right', component: () => import('pages/testRight.vue') }, { path: 'right', component: () => import('pages/testRight.vue') },
{ path: 'domain', component: () => import('pages/UserDomain.vue') }
], ],
}, },
// Always leave this as last one, // Always leave this as last one,

View File

@@ -0,0 +1,47 @@
import { defineStore } from 'pinia';
import { ActionFlow,AppInfo } from 'src/types/ActionTypes';
import {FlowCtrl } from '../control/flowCtrl';
export interface FlowEditorState{
flowNames1:string;
appInfo?:AppInfo;
flows?:ActionFlow[];
selectedFlow?:ActionFlow|undefined;
}
const flowCtrl=new FlowCtrl();
export const useFlowEditorStore = defineStore("flowEditor",{
state: ():FlowEditorState => ({
flowNames1: '',
appInfo:undefined,
flows:undefined,
selectedFlow:undefined
}),
getters: {
currentFlow():ActionFlow|undefined{
return this.selectedFlow;
}
},
actions: {
setFlows(flows:ActionFlow[]){
this.flows=flows;
},
selectFlow(flow:ActionFlow){
this.selectedFlow=flow;
},
setApp(app:AppInfo){
this.appInfo=app;
},
async setFlow(){
if(this.appInfo===undefined) return;
const actionFlows = await flowCtrl.getFlows(this.appInfo?.appId);
if(actionFlows && actionFlows.length>0){
this.setFlows(actionFlows);
}
if(actionFlows && actionFlows.length==1){
this.selectFlow(actionFlows[0]);
}
}
}
});

View File

@@ -0,0 +1,32 @@
import { store } from 'quasar/wrappers'
import { createPinia } from 'pinia'
import { Router } from 'vue-router';
/*
* When adding new properties to stores, you should also
* extend the `PiniaCustomProperties` interface.
* @see https://pinia.vuejs.org/core-concepts/plugins.html#typing-new-store-properties
*/
declare module 'pinia' {
export interface PiniaCustomProperties {
readonly router: Router;
}
}
/*
* If not building with SSR mode, you can
* directly export the Store instantiation;
*
* The function below can be async too; either use
* async/await or return a Promise which resolves
* with the Store instance.
*/
export default store((/* { ssrContext } */) => {
const pinia = createPinia()
// You can add Pinia plugins here
// pinia.use(SomePiniaPlugin)
return pinia
})

10
frontend/src/stores/store-flag.d.ts vendored Normal file
View File

@@ -0,0 +1,10 @@
/* eslint-disable */
// THIS FEATURE-FLAG FILE IS AUTOGENERATED,
// REMOVAL OR CHANGES WILL CAUSE RELATED TYPES TO STOP WORKING
import "quasar/dist/types/feature-flag";
declare module "quasar/dist/types/feature-flag" {
interface QuasarFeatureFlags {
store: true;
}
}

View File

@@ -0,0 +1,19 @@
import { defineStore } from 'pinia';
export const useCounterStore = defineStore('counter', {
state: () => ({
counter: 0
}),
getters: {
doubleCount (state) {
return state.counter * 2;
}
},
actions: {
increment () {
this.counter++;
}
}
});

View File

@@ -0,0 +1,36 @@
import { defineStore } from 'pinia';
import { api } from 'boot/axios';
import { Router } from '../router';
export const useAuthStore = defineStore({
id: 'auth',
state: () => ({
token: localStorage.getItem('token'),
returnUrl: ''
}),
actions: {
async login(username:string, password:string) {
const params = new URLSearchParams();
params.append('username', username);
params.append('password', password);
try{
const result = await api.post(`http://127.0.0.1:8000/api/token`,params);
console.info(result);
this.token =result.data.access_token;
localStorage.setItem('token', result.data.access_token);
Router.push(this.returnUrl || '/');
return true;
}catch(e)
{
console.info(e);
return false;
}
},
logout() {
this.token = null;
localStorage.removeItem('token');
Router.push('/login');
}
}
});

View File

@@ -1,5 +1,13 @@
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
/**
* アプリ情報
*/
export interface AppInfo {
appId:string;
code?:string;
name:string;
description?:string;
}
/** /**
* アクションのプロパティ定義 * アクションのプロパティ定義
@@ -41,6 +49,7 @@ export interface IActionNode{
* アクションフローの定義 * アクションフローの定義
*/ */
export interface IActionFlow { export interface IActionFlow {
id:string;
actionNodes:Array<IActionNode> actionNodes:Array<IActionNode>
} }
@@ -112,7 +121,7 @@ export class ActionNode implements IActionNode {
title:string, title:string,
inputPoint:string, inputPoint:string,
outputPoint: Array<string> = [], outputPoint: Array<string> = [],
actionProps: Array<IActionProperty> =[ActionProperty.defaultProperty()] actionProps: Array<IActionProperty> =[ActionProperty.defaultProperty()],
) { ) {
this.id=uuidv4(); this.id=uuidv4();
this.name = name; this.name = name;
@@ -168,7 +177,7 @@ export class RootAction implements IActionNode {
* アクションフローの定義 * アクションフローの定義
*/ */
export class ActionFlow implements IActionFlow { export class ActionFlow implements IActionFlow {
id:string;
actionNodes:Array<IActionNode>; actionNodes:Array<IActionNode>;
constructor(actionNodes:Array<IActionNode>|RootAction){ constructor(actionNodes:Array<IActionNode>|RootAction){
if(actionNodes instanceof Array){ if(actionNodes instanceof Array){
@@ -176,6 +185,7 @@ export class ActionFlow implements IActionFlow {
}else{ }else{
this.actionNodes=[actionNodes]; this.actionNodes=[actionNodes];
} }
this.id=uuidv4();
} }
/** /**
* ノードを追加する * ノードを追加する
@@ -194,13 +204,18 @@ export class ActionFlow implements IActionFlow {
if(inputPoint!==undefined){ if(inputPoint!==undefined){
newNode.inputPoint=inputPoint; newNode.inputPoint=inputPoint;
} }
if(prevNode){ if(prevNode!==undefined){
this.connectNodes(prevNode,newNode,inputPoint||''); this.connectNodes(prevNode,newNode,inputPoint||'');
}else{ }else{
prevNode=this.actionNodes[this.actionNodes.length-1]; prevNode=this.actionNodes[this.actionNodes.length-1];
this.connectNodes(prevNode,newNode,inputPoint||''); this.connectNodes(prevNode,newNode,inputPoint||'');
} }
const index=this.actionNodes.findIndex(node=>node.id===prevNode?.id)
if(index>=0){
this.actionNodes.splice(index+1,0,newNode);
}else{
this.actionNodes.push(newNode); this.actionNodes.push(newNode);
}
return newNode; return newNode;
} }
/** /**
@@ -291,6 +306,7 @@ reconnectOrRemoveNextNodes(targetNode: IActionNode): void {
//二つ以上の場合 //二つ以上の場合
for(const [point,nextid] of nextNodeIds){ for(const [point,nextid] of nextNodeIds){
const nextNode = this.findNodeById(nextid); const nextNode = this.findNodeById(nextid);
if(!nextNode) return;
if(!this.connectNodes(prevNode,nextNode,point)){ if(!this.connectNodes(prevNode,nextNode,point)){
this.removeAllNext(nextid); this.removeAllNext(nextid);
this.removeFromActionNodes(nextid); this.removeFromActionNodes(nextid);
@@ -353,5 +369,38 @@ reconnectOrRemoveNextNodes(targetNode: IActionNode): void {
return this.actionNodes.find((node) => node.id === id); return this.actionNodes.find((node) => node.id === id);
} }
toJSON() {
return {
id:this.id,
actionNodes: this.actionNodes.map(node => {
const { nextNodeIds, ...rest } = node;
return {
...rest,
nextNodeIds: Array.from(nextNodeIds.entries())
};
})
};
}
getRoot():IActionNode|undefined{
return this.actionNodes.find(node=>node.isRoot)
}
static fromJSON(json: string): ActionFlow {
const parsedObject = JSON.parse(json);
const actionNodes = parsedObject.actionNodes.map((node: any) => {
const nodeClass = !node.isRoot? new ActionNode(node.name,node.title,node.inputPoint,node.outputPoint,node.actionProps)
:new RootAction(node.name,node.title,node.subTitle);
nodeClass.nextNodeIds=new Map(node.nextNodeIds);
nodeClass.prevNodeId=node.prevNodeId;
return nodeClass;
});
const actionFlow = new ActionFlow(actionNodes);
actionFlow.id=parsedObject.id;
return actionFlow;
}
} }

View File

@@ -0,0 +1,138 @@
import {IActionFlow} from './ActionTypes';
export interface TreeNode {
label: string;
}
export interface KintoneEvent extends TreeNode {
eventId: string;
hasFlow: boolean;
flowData?: IActionFlow;
}
export interface KintoneScreen extends TreeNode {
label: string;
events: KintoneEvent[];
}
export class KintoneEventManager {
public screens: KintoneScreen[];
constructor(screens: KintoneScreen[]) {
this.screens = screens;
}
public findEventById(eventId: string): KintoneEvent | null {
for (const screen of this.screens) {
for (const event of screen.events) {
if (event.eventId === eventId) {
return event;
}
}
}
return null;
}
}
export const kintoneEvents:KintoneEventManager = new KintoneEventManager([
{
label:"レコード追加画面",
events:[
{
label:"レコード追加画面を表示した後",
eventId:"app.record.create.show",
hasFlow:false
},
{
label:"保存をクリックしたとき",
eventId:"app.record.create.submit",
hasFlow:true
},
{
label:"保存が成功したとき",
eventId:"app.record.create.submit.success ",
hasFlow:false
},
{
label:"フィールドの値を変更したとき",
eventId:"app.record.create.change",
hasFlow:false
},
]
},
{
label:"レコード詳細画面",
events:[
{
label:"レコード詳細画面を表示した後",
eventId:"app.record.detail.show",
hasFlow:false
},
{
label:"レコードを削除するとき",
eventId:"app.record.detail.delete.submit",
hasFlow:false
},
{
label:"プロセス管理のアクションを実行したとき",
eventId:"app.record.detail.process.proceed",
hasFlow:false
},
]
},
{
label:"レコード編集画面",
events:[
{
label:"レコード編集画面を表示した後",
eventId:"app.record.edit.show",
hasFlow:false
},
{
label:"保存をクリックしたとき",
eventId:"app.record.edit.submit",
hasFlow:false
},
{
label:"保存が成功したとき",
eventId:"app.record.edit.submit.success",
hasFlow:false
},
{
label:"フィールドの値を変更したとき",
eventId:"app.record.edit.change",
hasFlow:false
},
]
},
{
label:"レコード一覧画面",
events:[
{
label:"一覧画面を表示した後",
eventId:"app.record.index.show",
hasFlow:false
},
{
label:"インライン編集を開始したとき",
eventId:"app.record.index.edit.show",
hasFlow:false
},
{
label:"インライン編集のフィールド値を変更したとき",
eventId:"app.record.index.edit.change",
hasFlow:false
},
{
label:"インライン編集の【保存】をクリックしたとき",
eventId:"app.record.index.edit.submit",
hasFlow:false
},
{
label:"インライン編集の保存が成功したとき",
eventId:"app.record.index.edit.submit.success",
hasFlow:false
},
]
}
]);

View File

@@ -2149,6 +2149,14 @@ picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.2, picomatch@^2.3.1:
resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz" resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz"
integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
pinia@^2.0.0, pinia@^2.1.6:
version "2.1.6"
resolved "https://registry.npmjs.org/pinia/-/pinia-2.1.6.tgz"
integrity sha512-bIU6QuE5qZviMmct5XwCesXelb5VavdOWKWaB17ggk++NUwQWWbP5YnsONTk3b752QkW9sACiR81rorpeOMSvQ==
dependencies:
"@vue/devtools-api" "^6.5.0"
vue-demi ">=0.14.5"
postcss-selector-parser@^6.0.9: postcss-selector-parser@^6.0.9:
version "6.0.13" version "6.0.13"
resolved "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz" resolved "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz"
@@ -2662,7 +2670,7 @@ type-is@~1.6.18:
media-typer "0.3.0" media-typer "0.3.0"
mime-types "~2.1.24" mime-types "~2.1.24"
typescript@^4.5.4, "typescript@>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta": typescript@^4.5.4, "typescript@>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta", typescript@>=4.4.4:
version "4.9.5" version "4.9.5"
resolved "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz" resolved "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz"
integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g== integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==
@@ -2734,6 +2742,11 @@ vary@~1.1.2:
optionalDependencies: optionalDependencies:
fsevents "~2.3.2" fsevents "~2.3.2"
vue-demi@>=0.14.5:
version "0.14.6"
resolved "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.6.tgz"
integrity sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==
vue-eslint-parser@^9.3.0: vue-eslint-parser@^9.3.0:
version "9.3.1" version "9.3.1"
resolved "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.3.1.tgz" resolved "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.3.1.tgz"
@@ -2754,7 +2767,7 @@ vue-router@^4.0.0, vue-router@^4.0.12:
dependencies: dependencies:
"@vue/devtools-api" "^6.5.0" "@vue/devtools-api" "^6.5.0"
vue@^3.0.0, vue@^3.2.0, vue@^3.2.25, vue@^3.2.29, vue@3.3.4: "vue@^2.6.14 || ^3.3.0", vue@^3.0.0, "vue@^3.0.0-0 || ^2.6.0", vue@^3.2.0, vue@^3.2.25, vue@^3.2.29, vue@3.3.4:
version "3.3.4" version "3.3.4"
resolved "https://registry.npmjs.org/vue/-/vue-3.3.4.tgz" resolved "https://registry.npmjs.org/vue/-/vue-3.3.4.tgz"
integrity sha512-VTyEYn3yvIeY1Py0WaYGZsXnz3y5UnGi62GjVEqvEGPl6nxbOrCXbVOTQWBEJUqAyTUk2uJ5JLVnYJ6ZzGbrSw== integrity sha512-VTyEYn3yvIeY1Py0WaYGZsXnz3y5UnGi62GjVEqvEGPl6nxbOrCXbVOTQWBEJUqAyTUk2uJ5JLVnYJ6ZzGbrSw==

138
sample.json Normal file
View File

@@ -0,0 +1,138 @@
{
"id": "681ecde3-4439-4210-9fdf-424c6af98f09",
"actionNodes": [
{
"id": "34dfd32e-ba1a-440f-bb46-a8a1999109cd",
"name": "app.record.create.submit",
"title": "レコード追加画面",
"subTitle": "保存するとき",
"inputPoint": "",
"outputPoints": [],
"isRoot": true,
"actionProps": [],
"ActionValue": {},
"nextNodeIds": [
[
"",
"ce07775d-9729-4516-a88c-78ee8f2f851e"
]
]
},
{
"id": "ce07775d-9729-4516-a88c-78ee8f2f851e",
"name": "自動採番",
"title": "文書番号を自動採番する",
"inputPoint": "",
"outputPoints": [],
"actionProps": [
{
"component": "InputText",
"props": {
"name": "displayName",
"displayName": "文書番号を自動採番する",
"placeholder": "表示を入力してください",
"modelValue": ""
}
},
{
"component": "InputText",
"props": {
"displayName": "フォーマット",
"modelValue": "",
"name": "format",
"placeholder": "フォーマットを入力してください"
}
},
{
"component": "FieldInput",
"props": {
"displayName": "採番項目",
"modelValue": "",
"name": "field",
"placeholder": "採番項目を選択してください"
}
}
],
"prevNodeId": "34dfd32e-ba1a-440f-bb46-a8a1999109cd",
"nextNodeIds": [
[
"",
"0d18c3c9-abee-44e5-83eb-82074316219b"
]
]
},
{
"id": "0d18c3c9-abee-44e5-83eb-82074316219b",
"name": "入力データ取得",
"title": "電話番号を取得する",
"inputPoint": "",
"outputPoints": [],
"actionProps": [
{
"component": "InputText",
"props": {
"name": "displayName",
"displayName": "表示名",
"placeholder": "表示を入力してください",
"modelValue": ""
}
}
],
"prevNodeId": "ce07775d-9729-4516-a88c-78ee8f2f851e",
"nextNodeIds": [
[
"",
"399d7c04-5345-4bf6-8da3-d745df554524"
]
]
},
{
"id": "399d7c04-5345-4bf6-8da3-d745df554524",
"name": "条件分岐",
"title": "電話番号入力形式チャック",
"inputPoint": "",
"outputPoints": [
"はい",
"いいえ"
],
"actionProps": [
{
"component": "InputText",
"props": {
"name": "displayName",
"displayName": "表示名",
"placeholder": "表示を入力してください",
"modelValue": ""
}
}
],
"prevNodeId": "0d18c3c9-abee-44e5-83eb-82074316219b",
"nextNodeIds": [
[
"いいえ",
"8173e6bc-3fa2-4403-b973-9368884e2dfa"
]
]
},
{
"id": "8173e6bc-3fa2-4403-b973-9368884e2dfa",
"name": "エラー表示",
"title": "エラー表示して保存しない",
"inputPoint": "いいえ",
"outputPoints": [],
"actionProps": [
{
"component": "InputText",
"props": {
"name": "displayName",
"displayName": "表示名",
"placeholder": "表示を入力してください",
"modelValue": ""
}
}
],
"prevNodeId": "399d7c04-5345-4bf6-8da3-d745df554524",
"nextNodeIds": []
}
]
}